├── .gitignore ├── README.md ├── docker-compose.yml ├── requirements.txt └── src └── main ├── agreements ├── __init__.py ├── contract.py ├── contract_attachment.py ├── contract_attachment_data.py ├── contract_attachment_data_repository.py ├── contract_attachment_dto.py ├── contract_attachment_status.py ├── contract_controller.py ├── contract_dto.py ├── contract_repository.py ├── contract_service.py └── contract_status.py ├── assignment ├── __init__.py ├── assignment_status.py ├── driver_assignment.py ├── driver_assignment_facade.py ├── driver_assignment_repository.py └── involved_drivers_summary.py ├── cabs_application.py ├── carfleet ├── __init__.py ├── car_class.py ├── car_type.py ├── car_type_active_counter.py ├── car_type_controller.py ├── car_type_dto.py ├── car_type_repository.py └── car_type_service.py ├── common ├── __init__.py ├── application_event_publisher.py ├── base_entity.py ├── event.py └── functional.py ├── config ├── __init__.py └── app_properties.py ├── contracts ├── __init__.py ├── application │ ├── __init__.py │ ├── acme │ │ ├── __init__.py │ │ ├── dynamic │ │ │ ├── __init__.py │ │ │ ├── document_operation_result.py │ │ │ └── document_resource_manager.py │ │ └── straigthforward │ │ │ ├── __init__.py │ │ │ ├── acme_contract_process_based_on_straightforward_document_model.py │ │ │ ├── acme_state_factory.py │ │ │ └── contract_result.py │ └── editor │ │ ├── __init__.py │ │ ├── commit_result.py │ │ ├── document_dto.py │ │ └── document_editor.py ├── legacy │ ├── __init__.py │ ├── base_aggregate_root.py │ ├── contract_2.py │ ├── document.py │ ├── document_status.py │ ├── ooparadigm.py │ ├── printable.py │ ├── unsupported_transition_exception.py │ ├── user.py │ ├── user_repository.py │ └── versionable.py └── model │ ├── __init__.py │ ├── content │ ├── __init__.py │ ├── content_version.py │ ├── document_content.py │ ├── document_content_repository.py │ └── document_number.py │ ├── content_id.py │ ├── document_header.py │ ├── document_header_repository.py │ └── state │ ├── __init__.py │ ├── dynamic │ ├── __init__.py │ ├── acme │ │ ├── __init__.py │ │ └── acme_contract_state_assembler.py │ ├── change_command.py │ ├── config │ │ ├── __init__.py │ │ ├── actions │ │ │ ├── __init__.py │ │ │ ├── change_verifier.py │ │ │ └── publish_event.py │ │ ├── events │ │ │ ├── __init__.py │ │ │ ├── document_event.py │ │ │ ├── document_published.py │ │ │ └── document_unpublished.py │ │ ├── predicates │ │ │ ├── __init__.py │ │ │ └── contentchange │ │ │ │ ├── __init__.py │ │ │ │ ├── negative_predicate.py │ │ │ │ └── positive_predicate.py │ │ └── statechange │ │ │ ├── __init__.py │ │ │ ├── author_is_not_averifier.py │ │ │ ├── content_not_empty_verifier.py │ │ │ ├── positive_verifier.py │ │ │ └── previous_state_verifier.py │ ├── state.py │ ├── state_builder.py │ └── state_config.py │ └── straightforward │ ├── __init__.py │ ├── acme │ ├── __init__.py │ ├── archived_state.py │ ├── draft_state.py │ ├── published_state.py │ └── verified_state.py │ └── base_state.py ├── core ├── __init__.py ├── const.py └── database.py ├── crm ├── __init__.py ├── claims │ ├── __init__.py │ ├── claim.py │ ├── claim_attachment.py │ ├── claim_attachment_repository.py │ ├── claim_controller.py │ ├── claim_dto.py │ ├── claim_number_generator.py │ ├── claim_repository.py │ ├── claim_service.py │ ├── claims_resolver.py │ ├── claims_resolver_repository.py │ └── status.py ├── client.py ├── client_controller.py ├── client_dto.py ├── client_repository.py ├── client_service.py ├── notification │ ├── __init__.py │ ├── client_notification_service.py │ └── driver_notification_service.py └── transitanalyzer │ ├── __init__.py │ ├── analyzed_addresses_dto.py │ ├── graph_transit_analyzer.py │ ├── populate_graph_service.py │ └── transit_analyzer_controller.py ├── dev.env.example ├── driverfleet ├── __init__.py ├── driver.py ├── driver_attribute.py ├── driver_attribute_dto.py ├── driver_attribute_name.py ├── driver_attribute_repository.py ├── driver_controller.py ├── driver_dto.py ├── driver_fee.py ├── driver_fee_repository.py ├── driver_fee_service.py ├── driver_license.py ├── driver_repository.py ├── driver_service.py └── driverreport │ ├── __init__.py │ ├── driver_report.py │ ├── driver_report_controller.py │ ├── sql_based_driver_report_creator.py │ └── travelleddistance │ ├── __init__.py │ ├── travelled_distance.py │ ├── travelled_distance_repository.py │ └── travelled_distance_service.py ├── dto └── __init__.py ├── geolocation ├── __init__.py ├── address │ ├── __init__.py │ ├── address.py │ ├── address_dto.py │ └── address_repository.py ├── distance.py ├── distance_calculator.py └── geocoding_service.py ├── invocing ├── __init__.py ├── invoice.py ├── invoice_generator.py └── invoice_repository.py ├── loyalty ├── __init__.py ├── awarded_miles.py ├── awards_account.py ├── awards_account_controller.py ├── awards_account_dto.py ├── awards_account_repository.py ├── awards_service.py ├── awards_service_impl.py ├── constant_until.py ├── miles.py ├── miles_json_mapper.py └── two_step_expiring_miles.py ├── money ├── __init__.py └── money.py ├── party ├── __init__.py ├── api │ ├── __init__.py │ ├── party_id.py │ ├── party_mapper.py │ └── role_object_factory.py ├── infra │ ├── __init__.py │ ├── party_relationship_repository_impl.py │ └── party_repository_impl.py ├── model │ ├── __init__.py │ ├── party │ │ ├── __init__.py │ │ ├── party.py │ │ ├── party_relationship.py │ │ ├── party_relationship_repository.py │ │ ├── party_repository.py │ │ └── party_role.py │ └── role │ │ ├── __init__.py │ │ └── party_based_role.py └── utils │ ├── __init__.py │ └── polymorphic_hash_map.py ├── pricing ├── __init__.py ├── tariff.py └── tariffs.py ├── repair ├── __init__.py ├── api │ ├── __init__.py │ ├── assistance_request.py │ ├── contract_manager.py │ ├── repair_process.py │ ├── repair_request.py │ └── resolve_result.py ├── legacy │ ├── __init__.py │ ├── dao │ │ ├── __init__.py │ │ └── user_dao.py │ ├── job │ │ ├── __init__.py │ │ ├── common_base_abstract_job.py │ │ ├── job_result.py │ │ ├── maintenance_job.py │ │ └── repair_job.py │ ├── parts │ │ ├── __init__.py │ │ └── parts.py │ ├── service │ │ ├── __init__.py │ │ ├── job_doer.py │ │ └── job_doer_paralell_models.py │ └── user │ │ ├── __init__.py │ │ ├── common_base_abstract_user.py │ │ ├── employee_driver.py │ │ ├── employee_driver_with_leased_car.py │ │ ├── employee_driver_with_own_car.py │ │ ├── signed_contract.py │ │ ├── subcontractor_driver.py │ │ ├── subcontractor_driver_with_own_car.py │ │ └── subcontractor_with_rented_car.py └── model │ ├── __init__.py │ ├── dict │ ├── __init__.py │ ├── party_relationships_dictionary.py │ └── party_roles_dictionary.py │ └── roles │ ├── __init__.py │ ├── assistance │ ├── __init__.py │ └── role_for_assistance.py │ ├── empty │ ├── __init__.py │ ├── customer.py │ └── insured.py │ └── repair │ ├── __init__.py │ ├── extended_insurance.py │ ├── repairing_result.py │ ├── role_for_repairer.py │ └── warranty.py ├── ride ├── __init__.py ├── change_destination_service.py ├── change_pickup_service.py ├── complete_transit_service.py ├── demand_service.py ├── details │ ├── __init__.py │ ├── status.py │ ├── transit_details.py │ ├── transit_details_dto.py │ ├── transit_details_facade.py │ └── transit_details_repository.py ├── events │ ├── __init__.py │ └── transit_completed.py ├── request_for_transit.py ├── request_for_transit_repository.py ├── request_transit_service.py ├── ride_service.py ├── start_transit_service.py ├── transit.py ├── transit_controller.py ├── transit_demand.py ├── transit_demand_repository.py ├── transit_dto.py └── transit_repository.py ├── tests ├── __init__.py ├── agreements │ ├── __init__.py │ └── test_contract_lifecycle.py ├── assignment │ ├── __init__.py │ └── test_driver_assignment.py ├── common │ ├── __init__.py │ ├── address_fixture.py │ ├── address_matcher.py │ ├── awards_account_fixture.py │ ├── car_type_fixture.py │ ├── claim_fixture.py │ ├── client_fixture.py │ ├── driver_fixture.py │ ├── fixtures.py │ ├── ride_fixture.py │ ├── stubbed_transit_price.py │ └── transit_fixture.py ├── contracts │ ├── __init__.py │ ├── application │ │ ├── __init__.py │ │ ├── dynamic │ │ │ ├── __init__.py │ │ │ ├── document_operation_result_assert.py │ │ │ └── test_acme_contract_manager_based_on_dynamic_state_model.py │ │ └── straightforward │ │ │ ├── __init__.py │ │ │ └── acme │ │ │ ├── __init__.py │ │ │ ├── contract_result_assert.py │ │ │ └── test_acme_contract_process_based_on_straightforward_state_model.py │ ├── legacy │ │ ├── __init__.py │ │ └── test_document.py │ └── model │ │ ├── __init__.py │ │ └── state │ │ ├── __init__.py │ │ ├── dynamic │ │ ├── __init__.py │ │ ├── fake_document_publisher.py │ │ └── test_acme_contract.py │ │ └── straightforward │ │ ├── __init__.py │ │ └── test_acme_contract.py ├── crm │ ├── __init__.py │ └── claims │ │ ├── __init__.py │ │ └── test_claim_automatic_resolving.py ├── distance │ ├── __init__.py │ └── test_distance.py ├── driverfleet │ ├── __init__.py │ ├── driverreport │ │ ├── __init__.py │ │ └── travelleddistance │ │ │ ├── __init__.py │ │ │ └── test_slot.py │ └── test_driver_license.py ├── entity │ ├── __init__.py │ └── test_miles.py ├── integration │ ├── __init__.py │ ├── test_analyze_nearby_transits_integration.py │ ├── test_award_miles_management_integration.py │ ├── test_calculate_driver_fee_integration.py │ ├── test_calculate_driver_periodic_payments_integration.py │ ├── test_calculate_driver_travelled_distance_integration.py │ ├── test_car_type_update_integration.py │ ├── test_claim_automatic_resolving_integration.py │ ├── test_contract_lifecycle_integration.py │ ├── test_create_driver_report_integration.py │ ├── test_driver_tracking_service_integration.py │ ├── test_expiring_miles_integration.py │ ├── test_graph_transit_analyzer_integration.py │ ├── test_populate_graph_service_integration.py │ ├── test_removing_award_miles_integration.py │ ├── test_tariff_recognizing_integration.py │ ├── test_transit_life_cycle_integration.py │ └── test_validate_driver_license_integration.py ├── money │ ├── __init__.py │ └── test_money.py ├── pricing │ ├── __init__.py │ ├── test_calculate_transit_price.py │ └── test_tariff.py ├── repair │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── test_repair_process.py │ │ └── vehicle_repair_assert.py │ └── legacy │ │ ├── __init__.py │ │ ├── job │ │ ├── __init__.py │ │ └── test_repair.py │ │ └── service │ │ ├── __init__.py │ │ └── test_job_doer.py ├── ride │ ├── __init__.py │ ├── test_calculate_transit_distance.py │ ├── test_request_for_transit.py │ ├── test_transit.py │ └── test_transit_demand.py ├── test_app.py └── ui │ ├── __init__.py │ └── test_calculate_transit_distance.py └── tracking ├── __init__.py ├── driver_position.py ├── driver_position_dto.py ├── driver_position_dtov_2.py ├── driver_position_repository.py ├── driver_session.py ├── driver_session_controller.py ├── driver_session_dto.py ├── driver_session_repository.py ├── driver_session_service.py ├── driver_tracking_controller.py └── driver_tracking_service.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # User-specific stuff: 7 | .idea/ 8 | .idea/workspace.xml 9 | .idea/tasks.xml 10 | .idea/dictionaries 11 | .idea/vcs.xml 12 | .idea/jsLibraryMappings.xml 13 | snip_scripts/ 14 | 15 | # Unit test / coverage reports 16 | htmlcov/ 17 | .tox/ 18 | .nox/ 19 | .coverage 20 | .coverage.* 21 | .cache 22 | nosetests.xml 23 | coverage.xml 24 | *.cover 25 | *.py,cover 26 | .hypothesis/ 27 | .pytest_cache/ 28 | cover/ 29 | 30 | # Distribution / packaging 31 | .Python 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | eggs/ 36 | .eggs/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | share/python-wheels/ 41 | *.egg-info/ 42 | .installed.cfg 43 | *.egg 44 | MANIFEST 45 | 46 | # Other 47 | *.db 48 | *.env 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Fork of legacyfighter/cabs-java to fastapi (Python) 2 | 3 | ____ 4 | 5 | # Rozwój kodu 6 | 7 | Kod będzie rozwijać się wraz z cotygodniową narracją szkoleniową. Zarówno pojawiać się w nim będą kolejne poprawki jak i odziedziczone po firmach partnerskich nowe moduły ;-) Jak to w prawdziwym legacy. 8 | 9 | # Przeglądanie kodu 10 | 11 | Poszczególne kroki refaktoryzacyjne najlepiej przeglądać używająć tagów. Każdy krok szkoleniowy, który opisany jest w odcinku Legacy Fighter posiada na końcu planszę z nazwą odpowiedniego taga. Porównać zmiany można robiąc diffa w stosunku do poprzedniego taga z narracji. 12 | 13 | 14 | _____________ 15 | 16 | # Requirements 17 | 18 | - Python 3.10+ 19 | 20 | ## Installation 21 | 22 | ```bash 23 | $ pip install -r requirements.txt 24 | ``` 25 | 26 | ## Running the app 27 | 28 | ```bash 29 | $ cd src/main/ 30 | $ python cabs_application.py 31 | ``` 32 | 33 | ## Test 34 | 35 | ```bash 36 | $ cd src/main/ 37 | $ pytest 38 | ``` 39 | 40 | Minimalny output z pytest: 41 | 42 | ```bash 43 | $ cd src/main/ 44 | $ pytest --show-capture=no --disable-warnings --tb=no 45 | ``` -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | neo4j: 5 | image: neo4j:4.0.3 6 | hostname: neo4j 7 | container_name: neo4j 8 | ports: 9 | - "7474:7474" 10 | - "7687:7687" 11 | volumes: 12 | - ./neo4j/plugins:/plugins 13 | environment: 14 | NEO4J_AUTH: neo4j/password 15 | NEO4J_dbms_logs_debug_level: DEBUG 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sqlmodel 2 | pydantic 3 | pydantic[dotenv] 4 | fastapi[all] 5 | fastapi-utils 6 | uvicorn[standard] 7 | pythondi 8 | pytz 9 | python-dateutil 10 | pytest 11 | tzlocal 12 | mockito 13 | freezegun 14 | neo4j 15 | py2neo 16 | fastapi-events 17 | httpx 18 | injector 19 | fastapi-injector 20 | -------------------------------------------------------------------------------- /src/main/agreements/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/agreements/__init__.py -------------------------------------------------------------------------------- /src/main/agreements/contract_attachment.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import hashlib 4 | import uuid as uuid_pkg 5 | 6 | from datetime import datetime 7 | from typing import Optional 8 | 9 | from sqlalchemy import Column, Enum 10 | from sqlalchemy.orm import relationship 11 | from sqlmodel import Field, Relationship 12 | 13 | from agreements.contract_attachment_status import ContractAttachmentStatus 14 | from common.base_entity import BaseEntity, new_uuid 15 | 16 | 17 | class ContractAttachment(BaseEntity, table=True): 18 | __table_args__ = {'extend_existing': True} 19 | 20 | contract_attachment_no: uuid_pkg.UUID = Field( 21 | default_factory=new_uuid, 22 | nullable=False, 23 | ) 24 | 25 | accepted_at: Optional[datetime] 26 | rejected_at: Optional[datetime] 27 | change_date: Optional[datetime] 28 | status: Optional[ContractAttachmentStatus] = Field( 29 | default=ContractAttachmentStatus.PROPOSED, 30 | sa_column=Column(Enum(ContractAttachmentStatus)) 31 | ) 32 | 33 | # @ManyToOne 34 | contract_id: Optional[int] = Field(default=None, foreign_key="contract.id") 35 | contract: Optional[Contract] = Relationship( 36 | sa_relationship=relationship( 37 | "agreements.contract.Contract", back_populates="attachments"), 38 | ) 39 | 40 | def __eq__(self, o): 41 | if not isinstance(o, ContractAttachment): 42 | return False 43 | return self.id is not None and self.id == o.id 44 | 45 | def __hash__(self): 46 | m = hashlib.md5() 47 | for s in ( 48 | self.id, 49 | self.accepted_at, 50 | self.rejected_at, 51 | self.change_date, 52 | self.status, 53 | ): 54 | m.update(str(s).encode('utf-8')) 55 | return int(m.hexdigest(), 16) 56 | -------------------------------------------------------------------------------- /src/main/agreements/contract_attachment_data.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | from datetime import datetime 3 | from typing import Any 4 | 5 | from sqlmodel import Field 6 | 7 | from sqlalchemy import Column, DateTime, LargeBinary 8 | from common.base_entity import BaseEntity 9 | 10 | 11 | class ContractAttachmentData(BaseEntity, table=True): 12 | __table_args__ = {'extend_existing': True} 13 | 14 | contract_attachment_no: uuid_pkg.UUID = Field( 15 | nullable=False, 16 | ) 17 | 18 | # @Lob 19 | # @Column(name = "data", columnDefinition="BLOB") 20 | data: bytes = Field(sa_column=Column('data', LargeBinary)) 21 | 22 | # @Column(nullable = false) 23 | creation_date: datetime = Field(default=datetime.now(), sa_column=Column(DateTime, nullable=False)) 24 | 25 | def __init__(self, *, contract_attachment_id: uuid_pkg.UUID, data: bytes, **kwargs: Any): 26 | super().__init__(**kwargs) 27 | self.contract_attachment_no = contract_attachment_id 28 | self.data = data 29 | 30 | def __eq__(self, o): 31 | if not isinstance(o, ContractAttachmentData): 32 | return False 33 | return self.id is not None and self.id == o.id 34 | 35 | def __hash__(self): 36 | return self.contract_attachment_no.int 37 | -------------------------------------------------------------------------------- /src/main/agreements/contract_attachment_data_repository.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | from typing import Optional, List, Set 3 | 4 | from injector import inject 5 | from sqlalchemy import text 6 | 7 | from sqlmodel import Session 8 | 9 | from agreements.contract_attachment_data import ContractAttachmentData 10 | 11 | 12 | class ContractAttachmentDataRepositoryImp: 13 | session: Session 14 | 15 | @inject 16 | def __init__(self, session: Session): 17 | self.session = session 18 | 19 | def find_by_contract_attachment_no_in( 20 | self, attachment_ids: List[uuid_pkg.UUID] 21 | ) -> Set[ContractAttachmentData]: 22 | return set(self.session.query(ContractAttachmentData).where( 23 | ContractAttachmentData.contract_attachment_no.in_(attachment_ids) 24 | ).all()) 25 | 26 | def delete_by_attachment_id(self, attachment_id: int): 27 | stmt = text(( 28 | "delete FROM ContractAttachmentData AS cad WHERE cad.contract_attachment_no =" 29 | " (SELECT ca.contract_attachment_no FROM ContractAttachment AS ca WHERE ca.id = :attachment_id)" 30 | )) 31 | stmt = stmt.params(attachment_id=attachment_id) 32 | self.session.execute(stmt) 33 | 34 | def save(self, contract_attachment_data: ContractAttachmentData) -> Optional[ContractAttachmentData]: 35 | self.session.add(contract_attachment_data) 36 | self.session.commit() 37 | self.session.refresh(contract_attachment_data) 38 | return contract_attachment_data 39 | 40 | -------------------------------------------------------------------------------- /src/main/agreements/contract_attachment_dto.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, Any 3 | 4 | from agreements.contract_attachment_status import ContractAttachmentStatus 5 | from agreements.contract_attachment import ContractAttachment 6 | from agreements.contract_attachment_data import ContractAttachmentData 7 | from pydantic import BaseModel 8 | 9 | 10 | class ContractAttachmentDTO(BaseModel): 11 | id: Optional[int] 12 | contract_id: Optional[int] 13 | data: Optional[bytes] 14 | creation_date: Optional[datetime] 15 | accepted_at: Optional[datetime] 16 | rejected_at: Optional[datetime] 17 | change_date: Optional[datetime] 18 | status: Optional[ContractAttachmentStatus] 19 | 20 | def __init__(self, *, attachment: ContractAttachment = None, data: ContractAttachmentData = None, **kwargs: Any): 21 | super().__init__(**kwargs) 22 | if data is not None: 23 | self.data = data.data 24 | self.creation_date = data.creation_date 25 | if attachment is not None: 26 | self.id = attachment.id 27 | self.contract_id = attachment.contract_id 28 | self.rejected_at = attachment.rejected_at 29 | self.accepted_at = attachment.accepted_at 30 | self.change_date = attachment.change_date 31 | self.status = attachment.status 32 | -------------------------------------------------------------------------------- /src/main/agreements/contract_attachment_status.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class ContractAttachmentStatus(enum.Enum): 5 | PROPOSED = 1 6 | ACCEPTED_BY_ONE_SIDE = 2 7 | ACCEPTED_BY_BOTH_SIDES = 3 8 | REJECTED = 4 9 | -------------------------------------------------------------------------------- /src/main/agreements/contract_dto.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional, Any, Set 3 | 4 | from agreements.contract_attachment_dto import ContractAttachmentDTO 5 | from agreements.contract_status import ContractStatus 6 | from agreements.contract_attachment_data import ContractAttachmentData 7 | from agreements.contract import Contract 8 | from pydantic import BaseModel 9 | 10 | 11 | class ContractDTO(BaseModel): 12 | id: Optional[int] 13 | subject: Optional[str] 14 | partner_name: Optional[str] 15 | creation_date: Optional[datetime] 16 | accepted_at: Optional[datetime] 17 | rejected_at: Optional[datetime] 18 | change_date: Optional[datetime] 19 | status: Optional[ContractStatus] 20 | contract_no: Optional[str] 21 | attachments: List[ContractAttachmentDTO] 22 | 23 | def __init__(self, *, contract: Contract = None, attachments: Set[ContractAttachmentData] = None, **data: Any): 24 | if "attachments" not in data: 25 | data["attachments"] = [] 26 | if contract is not None: 27 | data.update(**contract.dict()) 28 | super().__init__(**data) 29 | if attachments: 30 | self.attachments = [] 31 | for attachment_data in attachments: 32 | contract_attachment_no = attachment_data.contract_attachment_no 33 | attachment = contract.find_attachment(contract_attachment_no) 34 | self.attachments.append(ContractAttachmentDTO( 35 | attachment=attachment, 36 | data=attachment_data, 37 | )) 38 | -------------------------------------------------------------------------------- /src/main/agreements/contract_repository.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | from typing import List, Optional 3 | 4 | from injector import inject 5 | from sqlalchemy import text 6 | 7 | from sqlmodel import Session 8 | 9 | from agreements.contract import Contract 10 | 11 | 12 | class ContractRepositoryImp: 13 | session: Session 14 | 15 | @inject 16 | def __init__(self, session: Session): 17 | self.session = session 18 | 19 | def save(self, contract: Contract) -> Optional[Contract]: 20 | self.session.add(contract) 21 | self.session.commit() 22 | self.session.refresh(contract) 23 | return contract 24 | 25 | def find_by_partner_name(self, partner_name: str) -> List[Contract]: 26 | return self.session.query(Contract).where(Contract.partner_name == partner_name).all() 27 | 28 | def find_by_attachment_id(self, attachment_id: int) -> Contract: 29 | stmt = text( 30 | "SELECT c.id, c.partner_name, c.subject, c.creation_date, c.accepted_at, c.rejected_at, c.change_date," 31 | " c.status, c.contract_no" 32 | " FROM contract as c JOIN ContractAttachment AS ca ON ca.contract_id = c.id WHERE ca.id = :attachment_id") 33 | return self.session.query(Contract).from_statement(stmt).params( 34 | attachment_id=attachment_id 35 | ).one_or_none() 36 | 37 | def find_contract_attachment_no_by_id(self, attachment_id: int) -> Optional[uuid_pkg.UUID]: 38 | stmt = text( 39 | "SELECT c.contract_attachment_no FROM ContractAttachment AS c WHERE c.id = :attachment_id" 40 | ) 41 | stmt = stmt.params(attachment_id=attachment_id) 42 | result = self.session.execute(stmt).one_or_none() 43 | return uuid_pkg.UUID(result[0]) if result else None 44 | 45 | def get_one(self, contract_id: int) -> Optional[Contract]: 46 | return self.session.query(Contract).filter(Contract.id == contract_id).first() 47 | -------------------------------------------------------------------------------- /src/main/agreements/contract_status.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class ContractStatus(enum.Enum): 5 | NEGOTIATIONS_IN_PROGRESS = 1 6 | REJECTED = 2 7 | ACCEPTED = 3 8 | -------------------------------------------------------------------------------- /src/main/assignment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/assignment/__init__.py -------------------------------------------------------------------------------- /src/main/assignment/assignment_status.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class AssignmentStatus(enum.Enum): 5 | CANCELLED = 1 6 | WAITING_FOR_DRIVER_ASSIGNMENT = 2 7 | DRIVER_ASSIGNMENT_FAILED = 3 8 | ON_THE_WAY = 4 9 | 10 | -------------------------------------------------------------------------------- /src/main/assignment/driver_assignment_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from uuid import UUID 3 | 4 | from injector import inject 5 | from sqlmodel import Session 6 | 7 | from assignment.assignment_status import AssignmentStatus 8 | from assignment.driver_assignment import DriverAssignment 9 | 10 | 11 | class DriverAssignmentRepository: 12 | session: Session 13 | 14 | @inject 15 | def __init__(self, session: Session): 16 | self.session = session 17 | 18 | def find_by_request_uuid(self, request_uuid: UUID) -> DriverAssignment: 19 | return self.session.query(DriverAssignment).where(DriverAssignment.request_uuid == request_uuid).first() 20 | 21 | def find_by_request_uuid_and_status(self, request_uuid: UUID, status: AssignmentStatus) -> DriverAssignment: 22 | return self.session.query(DriverAssignment).where( 23 | DriverAssignment.request_uuid == request_uuid 24 | ).where( 25 | DriverAssignment.status == status 26 | ).first() 27 | 28 | def save(self, driver_assignment: DriverAssignment) -> Optional[DriverAssignment]: 29 | self.session.add(driver_assignment) 30 | self.session.commit() 31 | self.session.refresh(driver_assignment) 32 | return driver_assignment 33 | -------------------------------------------------------------------------------- /src/main/assignment/involved_drivers_summary.py: -------------------------------------------------------------------------------- 1 | from typing import Set, Optional 2 | 3 | from assignment.assignment_status import AssignmentStatus 4 | 5 | 6 | class InvolvedDriversSummary: 7 | proposed_drivers: Set[int] = set() 8 | driver_rejections: Set[int] = set() 9 | assigned_driver: int = 0 10 | status: AssignmentStatus 11 | 12 | def __init__( 13 | self, 14 | proposed_drivers: Set[int], 15 | driver_rejections: Set[int], 16 | assigned_driver_id: Optional[int], 17 | status: AssignmentStatus, 18 | ): 19 | self.proposed_drivers = proposed_drivers 20 | self.driver_rejections = driver_rejections 21 | self.status = status 22 | 23 | @classmethod 24 | def none_found(cls) -> 'InvolvedDriversSummary': 25 | return cls(set(), set(), None, AssignmentStatus.DRIVER_ASSIGNMENT_FAILED) 26 | -------------------------------------------------------------------------------- /src/main/carfleet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/carfleet/__init__.py -------------------------------------------------------------------------------- /src/main/carfleet/car_class.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class CarClass(enum.IntEnum): 5 | ECO = 1 6 | REGULAR = 2 7 | VAN = 3 8 | PREMIUM = 4 9 | -------------------------------------------------------------------------------- /src/main/carfleet/car_type.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from typing import Optional 3 | 4 | from carfleet.car_class import CarClass 5 | from common.base_entity import BaseEntity 6 | from sqlalchemy import Column, Enum, Integer 7 | from sqlmodel import Field 8 | 9 | 10 | class CarType(BaseEntity, table=True): 11 | __table_args__ = {'extend_existing': True} 12 | 13 | class Status(enum.IntEnum): 14 | INACTIVE = 1 15 | ACTIVE = 2 16 | 17 | # @Enumerated(EnumType.STRING) 18 | # @Column(nullable = false) 19 | car_class: CarClass = Field(sa_column=Column(Enum(CarClass), nullable=False)) 20 | description: Optional[str] 21 | # @Enumerated(EnumType.STRING) 22 | status: Optional[Status] = Field(default=Status.INACTIVE, sa_column=Column(Enum(Status))) 23 | # @Column(nullable = false) 24 | cars_counter: int = Field(default=0, sa_column=Column(Integer, nullable=False)) 25 | # @Column(nullable = false) 26 | min_no_of_cars_to_activate_class: int = Field(sa_column=Column(Integer, nullable=False)) 27 | 28 | def register_car(self): 29 | self.cars_counter += 1 30 | 31 | def unregister_car(self): 32 | self.cars_counter += 1 33 | if self.cars_counter < 0: 34 | raise ValueError() 35 | 36 | def activate(self): 37 | if self.cars_counter < self.min_no_of_cars_to_activate_class: 38 | raise ValueError( 39 | f"Cannot activate car class when less than {self.min_no_of_cars_to_activate_class} cars in the fleet" 40 | ) 41 | self.status = self.Status.ACTIVE 42 | 43 | def deactivate(self): 44 | self.status = self.Status.INACTIVE 45 | 46 | def __eq__(self, o): 47 | if not isinstance(o, CarType): 48 | return False 49 | return self.id is not None and self.id == o.id 50 | 51 | -------------------------------------------------------------------------------- /src/main/carfleet/car_type_active_counter.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from sqlalchemy import Column, Integer 4 | from sqlmodel import SQLModel, Field 5 | 6 | from carfleet.car_class import CarClass 7 | from common.base_entity import EnumAsInteger 8 | 9 | 10 | class CarTypeActiveCounter(SQLModel, table=True): 11 | __table_args__ = {'extend_existing': True} 12 | 13 | car_class: CarClass = Field( 14 | primary_key=True, 15 | sa_column=Column(EnumAsInteger( 16 | CarClass, 17 | ), nullable=False, primary_key=True) 18 | ) 19 | 20 | active_cars_counter: int = Field(default=0, sa_column=Column(Integer, nullable=False)) 21 | 22 | def __init__(self, car_class: CarClass, **data: Any): 23 | super().__init__(**data) 24 | self.car_class = car_class 25 | 26 | def __eq__(self, o): 27 | if not isinstance(o, CarTypeActiveCounter): 28 | return False 29 | return self.car_class is not None and self.car_class == o.car_class 30 | 31 | -------------------------------------------------------------------------------- /src/main/carfleet/car_type_controller.py: -------------------------------------------------------------------------------- 1 | from fastapi_injector import Injected 2 | 3 | from carfleet.car_class import CarClass 4 | from carfleet.car_type_dto import CarTypeDTO 5 | from carfleet.car_type import CarType 6 | from fastapi_utils.cbv import cbv 7 | from fastapi_utils.inferring_router import InferringRouter 8 | from carfleet.car_type_service import CarTypeService 9 | 10 | car_type_router = InferringRouter(tags=["CarTypeController"]) 11 | 12 | @cbv(car_type_router) 13 | class CarTypeController: 14 | 15 | car_type_service: CarTypeService = Injected(CarTypeService) 16 | 17 | @car_type_router.post("/cartypes") 18 | def create(self, car_type_dto: CarTypeDTO) -> CarTypeDTO: 19 | created: CarTypeDTO = self.car_type_service.create(car_type_dto) 20 | return created 21 | 22 | @car_type_router.post("/cartypes/{car_class}/registerCar") 23 | def register_car(self, car_class: CarClass) -> dict: 24 | self.car_type_service.register_car(car_class) 25 | return {} 26 | 27 | @car_type_router.post("/cartypes/{car_class}/unregisterCar") 28 | def unregister_car(self, car_class: CarClass) -> dict: 29 | self.car_type_service.unregister_car(car_class) 30 | return {} 31 | 32 | @car_type_router.post("/cartypes/{car_type_id}/activate") 33 | def activate(self, car_type_id: int) -> dict: 34 | self.car_type_service.activate(car_type_id) 35 | return {} 36 | 37 | @car_type_router.post("/cartypes/{car_type_id}/deactivate") 38 | def deactivate(self, car_type_id: int) -> dict: 39 | self.car_type_service.deactivate(car_type_id) 40 | return {} 41 | 42 | @car_type_router.get("/cartypes/{car_type_id}") 43 | def find(self, car_type_id: int) -> CarTypeDTO: 44 | car_type = self.car_type_service.load_dto(car_type_id) 45 | return car_type 46 | -------------------------------------------------------------------------------- /src/main/carfleet/car_type_dto.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from carfleet.car_class import CarClass 4 | from carfleet.car_type import CarType 5 | from pydantic import BaseModel 6 | 7 | 8 | class CarTypeDTO(BaseModel): 9 | id: Optional[int] 10 | car_class: Optional[CarClass] 11 | status: Optional[CarType.Status] 12 | cars_counter: Optional[int] = 0 13 | description: Optional[str] 14 | active_cars_counter: Optional[int] = 0 15 | min_no_of_cars_to_activate_class: Optional[int] = 0 16 | 17 | def __init__(self, *, active_cars_counter=0, **data: Any): 18 | super().__init__(**data) 19 | self.active_cars_counter = active_cars_counter 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/common/__init__.py -------------------------------------------------------------------------------- /src/main/common/application_event_publisher.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | from datetime import datetime 3 | from typing import Any 4 | 5 | 6 | class EventObject: 7 | _source: Any 8 | 9 | def __init__(self, source: Any): 10 | if source is None: 11 | raise AttributeError("null source") 12 | 13 | self._source = source 14 | 15 | @property 16 | def source(self): 17 | return self._source 18 | 19 | def to_string(self): 20 | return f"{str(self.__class__.__name__)}[source={self._source}]" 21 | 22 | def __str__(self): 23 | return self.to_string() 24 | 25 | 26 | class ApplicationEvent(EventObject, metaclass=ABCMeta): 27 | __timestamp: int 28 | 29 | def __init__(self, source: Any): 30 | super().__init__(source) 31 | self.__timestamp = round(datetime.now().timestamp()) 32 | 33 | @property 34 | def timestamp(self): 35 | return self.__timestamp 36 | 37 | 38 | class ApplicationEventPublisher(metaclass=ABCMeta): 39 | def publish_event(self, event: ApplicationEvent) -> None: 40 | self.publish_event_object(event) 41 | 42 | @abstractmethod 43 | def publish_event_object(self, event: Any) -> None: 44 | ... 45 | -------------------------------------------------------------------------------- /src/main/common/base_entity.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from sqlalchemy import types 5 | from sqlmodel import Field, SQLModel 6 | 7 | 8 | def new_uuid() -> uuid.UUID: 9 | # Note: Work around UUIDs with leading zeros: https://github.com/tiangolo/sqlmodel/issues/25 10 | # by making sure uuid str does not start with a leading 0 11 | val = uuid.uuid4() 12 | while val.hex[0] == '0': 13 | val = uuid.uuid4() 14 | 15 | return val 16 | 17 | 18 | class BaseEntity(SQLModel): 19 | 20 | id: Optional[int] = Field(default=None, primary_key=True) 21 | version: int = 1 22 | 23 | 24 | class EnumAsInteger(types.TypeDecorator): 25 | """Column type for storing Python enums in a database INTEGER column. 26 | 27 | This will behave erratically if a database value does not correspond to 28 | a known enum value. 29 | """ 30 | impl = types.Integer # underlying database type 31 | 32 | def __init__(self, enum_type): 33 | super(EnumAsInteger, self).__init__() 34 | self.enum_type = enum_type 35 | 36 | def process_bind_param(self, value, dialect): 37 | if isinstance(value, self.enum_type): 38 | return value.value 39 | raise ValueError(f'expected {self.enum_type.__name__} value, got {value.__class__.__name__}') 40 | 41 | def process_result_value(self, value, dialect): 42 | return self.enum_type(value) 43 | 44 | def copy(self, **kwargs): 45 | return EnumAsInteger(self.enum_type) 46 | -------------------------------------------------------------------------------- /src/main/common/event.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class Event(metaclass=abc.ABCMeta): 5 | pass 6 | -------------------------------------------------------------------------------- /src/main/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/config/__init__.py -------------------------------------------------------------------------------- /src/main/config/app_properties.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | from pydantic import BaseSettings 4 | 5 | 6 | class AppProperties(BaseSettings): 7 | no_of_transits_for_claim_automatic_refund: int 8 | automatic_refund_for_vip_threshold: int 9 | min_no_of_cars_for_eco_class: int 10 | miles_expiration_in_days: int = 365 11 | default_miles_bonus: int = 10 12 | sqlite_url: str 13 | 14 | class Config: 15 | env_file = 'dev.env' 16 | env_file_encoding = 'utf-8' 17 | 18 | 19 | @lru_cache() 20 | def get_app_properties(): 21 | return AppProperties() 22 | -------------------------------------------------------------------------------- /src/main/contracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/application/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/application/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/application/acme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/application/acme/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/application/acme/dynamic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/application/acme/dynamic/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/application/acme/straigthforward/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/application/acme/straigthforward/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/application/acme/straigthforward/acme_state_factory.py: -------------------------------------------------------------------------------- 1 | from contracts.model.document_header import DocumentHeader 2 | from contracts.model.state.straightforward.acme.draft_state import DraftState 3 | from contracts.model.state.straightforward.base_state import BaseState 4 | 5 | 6 | class AcmeStateFactory: 7 | @staticmethod 8 | def format_class_key(clazz): 9 | return f"{clazz.__module__}.{clazz.__name__}" 10 | 11 | def create(self, header: DocumentHeader) -> BaseState: 12 | # sample impl is based on class names 13 | # other possibilities: names Dependency Injection Containers, states persisted via ORM Discriminator mechanism, 14 | # mapper 15 | class_name: str = header.state_descriptor 16 | 17 | if not class_name: 18 | state: DraftState = DraftState() 19 | state.init(header) 20 | return state 21 | 22 | class_full_path = class_name.split(".") 23 | cast_class = __import__(class_full_path[0]) 24 | for elem in class_full_path[1:]: 25 | cast_class = getattr(cast_class, elem) 26 | 27 | try: 28 | state: BaseState = cast_class() 29 | state.init(header) 30 | return state 31 | except Exception as e: 32 | raise e 33 | -------------------------------------------------------------------------------- /src/main/contracts/application/acme/straigthforward/contract_result.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from contracts.model.content.document_number import DocumentNumber 4 | 5 | 6 | class ContractResult: 7 | class Result(enum.Enum): 8 | FAILURE = enum.auto() 9 | SUCCESS = enum.auto() 10 | 11 | __result: Result 12 | __document_header_id: int 13 | __document_number: DocumentNumber 14 | __state_descriptor: str 15 | 16 | def __init__( 17 | self, 18 | result: Result, 19 | document_header_id: int, 20 | document_number: DocumentNumber, 21 | state_descriptor: str, 22 | ): 23 | self.__result = result 24 | self.__document_header_id = document_header_id 25 | self.__document_number = document_number 26 | self.__state_descriptor = state_descriptor 27 | 28 | @property 29 | def result(self): 30 | return self.__result 31 | 32 | @property 33 | def document_number(self): 34 | return self.__document_number 35 | 36 | @property 37 | def document_header_id(self): 38 | return self.__document_header_id 39 | 40 | @property 41 | def state_descriptor(self): 42 | return self.__state_descriptor 43 | -------------------------------------------------------------------------------- /src/main/contracts/application/editor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/application/editor/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/application/editor/commit_result.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from typing import Optional 3 | from uuid import UUID 4 | 5 | 6 | class CommitResult: 7 | class Result(enum.Enum): 8 | FAILURE = enum.auto() 9 | SUCCESS = enum.auto() 10 | 11 | __content_id: UUID 12 | __result: Result 13 | __message: Optional[str] 14 | 15 | def __init__(self, content_id: UUID, result: Result, message: Optional[str] = None): 16 | self.__content_id = content_id 17 | self.__result = result 18 | self.__message = message 19 | 20 | @property 21 | def result(self): 22 | return self.__result 23 | 24 | @property 25 | def content_id(self): 26 | return self.__content_id 27 | -------------------------------------------------------------------------------- /src/main/contracts/application/editor/document_dto.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from uuid import UUID 3 | 4 | from contracts.model.content.content_version import ContentVersion 5 | 6 | 7 | class DocumentDTO: 8 | __content_id: UUID 9 | __physical_content: str 10 | __content_version: ContentVersion 11 | 12 | def __init__(self, content_id: Optional[UUID], physical_content: str, content_version: ContentVersion): 13 | self.__content_id = content_id 14 | self.__physical_content = physical_content 15 | self.__content_version = content_version 16 | 17 | @property 18 | def content_id(self) -> UUID: 19 | return self.__content_id 20 | 21 | @property 22 | def physical_content(self) -> str: 23 | return self.__physical_content 24 | 25 | @property 26 | def content_version(self) -> ContentVersion: 27 | return self.__content_version 28 | -------------------------------------------------------------------------------- /src/main/contracts/application/editor/document_editor.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from injector import inject 4 | 5 | from contracts.application.editor.commit_result import CommitResult 6 | from contracts.application.editor.document_dto import DocumentDTO 7 | from contracts.model.content.document_content import DocumentContent 8 | from contracts.model.content.document_content_repository import DocumentContentRepository 9 | 10 | 11 | class DocumentEditor: 12 | document_content_repository: DocumentContentRepository 13 | 14 | @inject 15 | def __init__( 16 | self, 17 | document_content_repository: DocumentContentRepository, 18 | ): 19 | self.document_content_repository = document_content_repository 20 | 21 | def commit(self, document: DocumentDTO): 22 | 23 | previous_id: UUID = document.content_id 24 | 25 | content: DocumentContent = DocumentContent( 26 | previous_id, 27 | document.content_version, 28 | document.physical_content 29 | ) 30 | self.document_content_repository.save(content) 31 | return CommitResult(content.id, CommitResult.Result.SUCCESS) 32 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/legacy/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/legacy/base_aggregate_root.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta 2 | 3 | from common.base_entity import BaseEntity 4 | 5 | 6 | class BaseAggregateRoot(BaseEntity, metaclass=ABCMeta): 7 | # just to be DDD compliant 8 | 9 | # if you have added a DDD skill to your linkedin profile just because extending this class add + below: 10 | # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | pass 12 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/contract_2.py: -------------------------------------------------------------------------------- 1 | import random 2 | import sys 3 | from typing import Any, Optional 4 | 5 | from contracts.legacy.document import Document 6 | from contracts.legacy.document_status import DocumentStatus 7 | from contracts.legacy.unsupported_transition_exception import UnsupportedTransitionException 8 | from contracts.legacy.user import User 9 | from contracts.legacy.versionable import Versionable 10 | 11 | 12 | class Contract2(Document, Versionable): 13 | 14 | def __init__(self, number: str, creator: User, **data: Any): 15 | super().__init__(number, creator, **data) 16 | 17 | def publish(self) -> None: 18 | raise UnsupportedTransitionException(self.status, DocumentStatus.PUBLISHED) 19 | 20 | def accept(self): 21 | if self.status == DocumentStatus.VERIFIED: 22 | self.status = DocumentStatus.PUBLISHED # reusing unused enum to provide data model for new status 23 | 24 | # Contracts just don't have a title, it's just a part of the content 25 | def change_title(self, title: str): 26 | super().change_title(title + self.content) 27 | 28 | # NOT @Override 29 | def change_content(self, content: str, user_status: Optional[str] = None) -> None: 30 | if user_status == "ChiefSalesOfficerStatus" or self.__mister_vladimir_is_logged_in(user_status): 31 | self.override_published = True 32 | self.change_content(content) 33 | 34 | def __mister_vladimir_is_logged_in(self, user_status: str): 35 | return user_status.lower().strip() == f"!!!id={self.NUMBER_OF_THE_BEAST}" 36 | 37 | NUMBER_OF_THE_BEAST: str = "616" 38 | 39 | def recreate_to(self, version: int): 40 | # TODO need to learn Kafka 41 | pass 42 | 43 | def get_last_version(self): 44 | return random.randint(0, sys.maxsize) 45 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/document_status.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class DocumentStatus(enum.Enum): 5 | DRAFT = 1 6 | VERIFIED = 2 7 | PUBLISHED = 3 8 | ARCHIVED = 4 9 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/ooparadigm.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class OOParadigm(metaclass=ABCMeta): 5 | # 2. enkapsulacja - ukrycie impl 6 | __filed: object 7 | 8 | # 1. abstrakcja - agent odbierający sygnały 9 | def method(self): 10 | pass 11 | # do sth 12 | 13 | # 3. polimorfizm - zmienne zachowania 14 | @abstractmethod 15 | def _abstract_step(self): 16 | raise NotImplementedError 17 | 18 | 19 | # 4. dziedziczenie - technika wspierająca polimorizm 20 | class ConcreteType(OOParadigm): 21 | 22 | def _abstract_step(self): 23 | pass 24 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/printable.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Printable(BaseModel, metaclass=ABCMeta): 7 | pass 8 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/unsupported_transition_exception.py: -------------------------------------------------------------------------------- 1 | from contracts.legacy.document_status import DocumentStatus 2 | 3 | 4 | class UnsupportedTransitionException(Exception): 5 | current: DocumentStatus 6 | desired: DocumentStatus 7 | 8 | def __init__(self, current: DocumentStatus, desired: DocumentStatus): 9 | super().__init__(f"can not transit form {current} to {desired}") 10 | self.current = current 11 | self.desired = desired 12 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/user.py: -------------------------------------------------------------------------------- 1 | from common.base_entity import BaseEntity 2 | 3 | 4 | class User(BaseEntity, table=True): 5 | __table_args__ = {'extend_existing': True} 6 | 7 | def __eq__(self, other): 8 | if not isinstance(other, User): 9 | return False 10 | return self.id == other.id 11 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/user_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | from sqlmodel import Session 5 | 6 | from contracts.legacy.user import User 7 | from core.database import get_session 8 | 9 | 10 | class UserRepository: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def save(self, user: User) -> Optional[User]: 18 | self.session.add(user) 19 | self.session.commit() 20 | self.session.refresh(user) 21 | return user 22 | 23 | def get_one(self, user_id) -> Optional[User]: 24 | statement = self.session.query(User).where(User.id == user_id) 25 | results = self.session.exec(statement) 26 | return results.scalar_one_or_none() 27 | -------------------------------------------------------------------------------- /src/main/contracts/legacy/versionable.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta 2 | 3 | 4 | class Versionable(metaclass=ABCMeta): 5 | def recreate_to(self, version: int): 6 | raise NotImplementedError 7 | 8 | def get_last_version(self) -> int: 9 | raise NotImplementedError 10 | -------------------------------------------------------------------------------- /src/main/contracts/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/content/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/content/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/content/content_version.py: -------------------------------------------------------------------------------- 1 | class ContentVersion: 2 | __content_version: str 3 | 4 | def __init__(self, content_version): 5 | self.__content_version = content_version 6 | 7 | @property 8 | def content_version(self): 9 | return self.__content_version 10 | -------------------------------------------------------------------------------- /src/main/contracts/model/content/document_content.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from typing import Any, Optional 4 | 5 | from sqlalchemy.orm import composite 6 | from sqlmodel import SQLModel, Field 7 | 8 | from common.base_entity import new_uuid 9 | from contracts.model.content.content_version import ContentVersion 10 | 11 | 12 | class DocumentContent(SQLModel, table=True): 13 | __table_args__ = {'extend_existing': True} 14 | 15 | id: uuid.UUID = Field( 16 | default_factory=new_uuid, 17 | nullable=False, 18 | primary_key=True 19 | ) 20 | 21 | previous_id: Optional[uuid.UUID] 22 | 23 | physical_content: str # some kind of reference to file, version control. In sour sample i will be a blob stored 24 | # in DB:) 25 | 26 | content_version: str 27 | __content_version: composite(ContentVersion, 'content_version') # just a human readable descriptor 28 | 29 | class Config: 30 | arbitrary_types_allowed = True 31 | 32 | def __init__( 33 | self, 34 | previous_id: uuid.UUID, 35 | content_version: ContentVersion, 36 | physical_content: str, **data: Any 37 | ): 38 | super().__init__(**data) 39 | self.previous_id = previous_id 40 | self.content_version = content_version.content_version 41 | self.physical_content = physical_content 42 | 43 | def get_document_version(self) -> ContentVersion: 44 | return self.__content_version 45 | -------------------------------------------------------------------------------- /src/main/contracts/model/content/document_content_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | from sqlmodel import Session 5 | 6 | from contracts.model.content.document_content import DocumentContent 7 | from core.database import get_session 8 | 9 | 10 | class DocumentContentRepository: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def save(self, document_content: DocumentContent) -> Optional[DocumentContent]: 18 | self.session.add(document_content) 19 | self.session.commit() 20 | self.session.refresh(document_content) 21 | return document_content 22 | 23 | def get_one(self, document_header_id) -> Optional[DocumentContent]: 24 | statement = self.session.query(DocumentContent).where(DocumentContent.id == document_header_id) 25 | results = self.session.exec(statement) 26 | return results.scalar_one_or_none() 27 | -------------------------------------------------------------------------------- /src/main/contracts/model/content/document_number.py: -------------------------------------------------------------------------------- 1 | class DocumentNumber: 2 | __number: str 3 | 4 | def __init__(self, number: str): 5 | self.__number = number 6 | 7 | @property 8 | def number(self): 9 | return self.__number 10 | -------------------------------------------------------------------------------- /src/main/contracts/model/content_id.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | 3 | 4 | class ContentId: 5 | content_id: uuid_pkg.UUID 6 | 7 | def __init__(self, content_id: uuid_pkg.UUID): 8 | self.content_id = content_id 9 | 10 | def to_string(self): 11 | return f"ContentId{{contentId={self.content_id}}}" 12 | 13 | def __str__(self): 14 | return self.to_string() 15 | 16 | def __eq__(self, other): 17 | if not isinstance(other, ContentId): 18 | return False 19 | return self.content_id is not None and str(self.content_id) == other.content_id 20 | -------------------------------------------------------------------------------- /src/main/contracts/model/document_header.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | from common.base_entity import BaseEntity 4 | from contracts.model.content.document_number import DocumentNumber 5 | from contracts.model.content_id import ContentId 6 | from sqlalchemy.orm import composite 7 | 8 | 9 | class DocumentHeader(BaseEntity, table=True): 10 | __table_args__ = {'extend_existing': True} 11 | 12 | number: str 13 | __number: Optional[DocumentNumber] = composite(DocumentNumber, 'number') 14 | 15 | author_id: Optional[int] 16 | 17 | verifier_id: Optional[int] 18 | 19 | state_descriptor: Optional[str] 20 | 21 | content_id: Optional[str] 22 | __content_id: Optional[ContentId] = composite(ContentId, 'content_id') 23 | 24 | class Config: 25 | arbitrary_types_allowed = True 26 | 27 | def __init__(self, author_id: int, number: DocumentNumber, **data: Any): 28 | super().__init__(**data) 29 | self.author_id = author_id 30 | self.number = number.number 31 | 32 | def change_current_content(self, content_id: ContentId) -> None: 33 | self.content_id = str(content_id.content_id) 34 | 35 | def not_empty(self): 36 | return self.content_id is not None 37 | 38 | def get_content_id(self) -> ContentId: 39 | return self.__content_id 40 | 41 | def get_document_number(self) -> DocumentNumber: 42 | return self.__number 43 | 44 | def get_verifier(self): 45 | return self.verifier_id 46 | -------------------------------------------------------------------------------- /src/main/contracts/model/document_header_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | from sqlmodel import Session 5 | 6 | from contracts.model.document_header import DocumentHeader 7 | from core.database import get_session 8 | 9 | 10 | class DocumentHeaderRepository: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def save(self, document_header: DocumentHeader) -> Optional[DocumentHeader]: 18 | self.session.add(document_header) 19 | self.session.commit() 20 | self.session.refresh(document_header) 21 | return document_header 22 | 23 | def get_one(self, document_header_id) -> Optional[DocumentHeader]: 24 | statement = self.session.query(DocumentHeader).where(DocumentHeader.id == document_header_id) 25 | results = self.session.exec(statement) 26 | return results.scalar_one_or_none() 27 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/acme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/acme/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/change_command.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Generic, TypeVar, Type 2 | 3 | T = TypeVar('T') 4 | 5 | 6 | class ChangeCommand: 7 | __desired_state: str 8 | __params: Dict[str, Any] 9 | 10 | def __init__( 11 | self, 12 | desired_state: str, 13 | params: Dict[str, Any] = None, 14 | ): 15 | self.__desired_state = desired_state 16 | self.__params = params or {} 17 | 18 | def with_param(self, name: str, value: Any) -> 'ChangeCommand': 19 | self.__params[name] = value 20 | return self 21 | 22 | def get_param(self, name: str, type_param: Type[T]) -> Generic[T]: 23 | return self.__params.get(name) 24 | 25 | def to_string(self): 26 | return f"ChangeCommand{{desiredState='{self.__desired_state}'}}" 27 | 28 | def __str__(self): 29 | return self.to_string() 30 | 31 | @property 32 | def desired_state(self): 33 | return self.__desired_state 34 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/config/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/config/actions/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/actions/change_verifier.py: -------------------------------------------------------------------------------- 1 | from common.functional import BiFunction 2 | from contracts.model.document_header import DocumentHeader 3 | from contracts.model.state.dynamic.change_command import ChangeCommand 4 | 5 | 6 | class ChangeVerifier(BiFunction[DocumentHeader, ChangeCommand, None]): 7 | PARAM_VERIFIER: str = "verifier" 8 | 9 | def apply(self, document_header: DocumentHeader, command: ChangeCommand) -> None: 10 | document_header.verifier_id = command.get_param(self.PARAM_VERIFIER, int) 11 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/actions/publish_event.py: -------------------------------------------------------------------------------- 1 | from typing import Type, Any 2 | 3 | from common.functional import BiFunction 4 | from contracts.model.document_header import DocumentHeader 5 | from contracts.model.state.dynamic.change_command import ChangeCommand 6 | from contracts.model.state.dynamic.config.events.document_event import DocumentEvent 7 | 8 | 9 | class PublishEvent(BiFunction[DocumentHeader, ChangeCommand, None]): 10 | __event_class: Type[DocumentEvent] 11 | 12 | publisher: Any 13 | 14 | def __init__(self, event_class: Type[DocumentEvent], publisher: Any): 15 | self.__event_class = event_class 16 | self.publisher = publisher 17 | 18 | def apply(self, document_header: DocumentHeader, command: ChangeCommand) -> None: 19 | event: DocumentEvent 20 | try: 21 | event = self.__event_class( 22 | document_header.id, 23 | document_header.state_descriptor, 24 | document_header.get_content_id(), 25 | document_header.get_document_number(), 26 | ) 27 | except Exception as e: 28 | raise e 29 | 30 | self.publisher.publish_event(event) 31 | 32 | return None 33 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/config/events/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/events/document_event.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta 2 | 3 | from common.application_event_publisher import ApplicationEvent 4 | from contracts.model.content.document_number import DocumentNumber 5 | from contracts.model.content_id import ContentId 6 | 7 | 8 | class DocumentEvent(ApplicationEvent, metaclass=ABCMeta): 9 | __document_id: int 10 | __current_sate: str 11 | __content_id: ContentId 12 | __number: DocumentNumber 13 | 14 | class Config: 15 | arbitrary_types_allowed = True 16 | 17 | def __init__( 18 | self, 19 | document_id: int, 20 | current_sate: str, 21 | content_id: ContentId, 22 | number: DocumentNumber, 23 | ): 24 | super().__init__(number) 25 | self.__document_id = document_id 26 | self.__current_sate = current_sate 27 | self.__content_id = content_id 28 | self.__number = number 29 | 30 | @property 31 | def document_id(self) -> int: 32 | return self.__document_id 33 | 34 | @property 35 | def current_sate(self) -> str: 36 | return self.__current_sate 37 | 38 | @property 39 | def content_id(self) -> ContentId: 40 | return self.__content_id 41 | 42 | @property 43 | def number(self) -> DocumentNumber: 44 | return self.__number 45 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/events/document_published.py: -------------------------------------------------------------------------------- 1 | from contracts.model.content.document_number import DocumentNumber 2 | from contracts.model.content_id import ContentId 3 | from contracts.model.state.dynamic.config.events.document_event import DocumentEvent 4 | 5 | 6 | class DocumentPublished(DocumentEvent): 7 | def __init__(self, document_id: int, current_sate: str, content_id: ContentId, number: DocumentNumber): 8 | super().__init__(document_id, current_sate, content_id, number) 9 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/events/document_unpublished.py: -------------------------------------------------------------------------------- 1 | from contracts.model.content.document_number import DocumentNumber 2 | from contracts.model.content_id import ContentId 3 | from contracts.model.state.dynamic.config.events.document_event import DocumentEvent 4 | 5 | 6 | class DocumentUnpublished(DocumentEvent): 7 | def __init__(self, document_id: int, current_sate: str, content_id: ContentId, number: DocumentNumber): 8 | super().__init__(document_id, current_sate, content_id, number) 9 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/predicates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/config/predicates/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/predicates/contentchange/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/config/predicates/contentchange/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/predicates/contentchange/negative_predicate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from common.functional import Predicate 4 | 5 | 6 | class NegativePredicate(Predicate['State']): 7 | 8 | def test(self, state: State) -> bool: 9 | return False 10 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/predicates/contentchange/positive_predicate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from common.functional import Predicate 4 | from contracts.model.state.dynamic.state import State 5 | 6 | 7 | class PositivePredicate(Predicate[State]): 8 | 9 | def test(self, state: State) -> bool: 10 | return True 11 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/statechange/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/dynamic/config/statechange/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/statechange/author_is_not_averifier.py: -------------------------------------------------------------------------------- 1 | from common.functional import BiFunction 2 | from contracts.model.state.dynamic.change_command import ChangeCommand 3 | from contracts.model.state.dynamic.config.actions.change_verifier import ChangeVerifier 4 | from contracts.model.state.dynamic.state import State 5 | 6 | 7 | class AuthorIsNotAVerifier(BiFunction[State, ChangeCommand, bool]): 8 | PARAM_VERIFIER: str = ChangeVerifier.PARAM_VERIFIER 9 | 10 | def apply(self, state: State, command: ChangeCommand = None) -> bool: 11 | return not command.get_param(self.PARAM_VERIFIER, int) == state.document_header.author_id 12 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/statechange/content_not_empty_verifier.py: -------------------------------------------------------------------------------- 1 | from common.functional import BiFunction 2 | from contracts.model.state.dynamic.change_command import ChangeCommand 3 | from contracts.model.state.dynamic.state import State 4 | 5 | 6 | class ContentNotEmptyVerifier(BiFunction[State, ChangeCommand, bool]): 7 | 8 | def apply(self, state: State, command: ChangeCommand = None) -> bool: 9 | return state.document_header.content_id is not None 10 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/statechange/positive_verifier.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from common.functional import BiFunction 4 | from contracts.model.state.dynamic.change_command import ChangeCommand 5 | 6 | 7 | class PositiveVerifier(BiFunction['State', ChangeCommand, bool]): 8 | 9 | def apply(self, state: State, command: ChangeCommand = None) -> bool: 10 | return True 11 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/config/statechange/previous_state_verifier.py: -------------------------------------------------------------------------------- 1 | from common.functional import BiFunction 2 | from contracts.model.state.dynamic.change_command import ChangeCommand 3 | from contracts.model.state.dynamic.state import State 4 | 5 | 6 | class PreviousStateVerifier(BiFunction[State, ChangeCommand, bool]): 7 | __state_descriptor: str 8 | 9 | def __init__(self, state_descriptor: str): 10 | self.__state_descriptor = state_descriptor 11 | 12 | def apply(self, state: State, command: ChangeCommand = None) -> bool: 13 | return state.state_descriptor == self.__state_descriptor 14 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/dynamic/state_config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABCMeta, abstractmethod 4 | 5 | 6 | class StateConfig(metaclass=ABCMeta): 7 | @abstractmethod 8 | def begin(self, document_header: DocumentHeader) -> State: 9 | ... 10 | 11 | @abstractmethod 12 | def recreate(self, document_header: DocumentHeader) -> State: 13 | ... 14 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/straightforward/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/acme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/contracts/model/state/straightforward/acme/__init__.py -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/acme/archived_state.py: -------------------------------------------------------------------------------- 1 | from contracts.model.document_header import DocumentHeader 2 | from contracts.model.state.straightforward.base_state import BaseState 3 | 4 | 5 | class ArchivedState(BaseState): 6 | def can_change_content(self) -> bool: 7 | return False 8 | 9 | def state_after_content_change(self) -> 'BaseState': 10 | return self 11 | 12 | def can_change_from(self, previous_state: 'BaseState'): 13 | return True 14 | 15 | def acquire(self, document_header: DocumentHeader) -> None: 16 | ... 17 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/acme/draft_state.py: -------------------------------------------------------------------------------- 1 | from contracts.model.document_header import DocumentHeader 2 | from contracts.model.state.straightforward.base_state import BaseState 3 | 4 | 5 | class DraftState(BaseState): 6 | # BAD IDEA! 7 | # def publish() -> BaseState: 8 | # if some validation 9 | # return PublishedState() 10 | 11 | def can_change_content(self) -> bool: 12 | return True 13 | 14 | def state_after_content_change(self) -> 'BaseState': 15 | return self 16 | 17 | def can_change_from(self, previous_state: 'BaseState'): 18 | return True 19 | 20 | def acquire(self, document_header: DocumentHeader) -> None: 21 | ... 22 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/acme/published_state.py: -------------------------------------------------------------------------------- 1 | from contracts.model.document_header import DocumentHeader 2 | from contracts.model.state.straightforward.acme.verified_state import VerifiedState 3 | from contracts.model.state.straightforward.base_state import BaseState 4 | 5 | 6 | class PublishedState(BaseState): 7 | 8 | def can_change_content(self) -> bool: 9 | return False 10 | 11 | def state_after_content_change(self) -> 'BaseState': 12 | return self 13 | 14 | def can_change_from(self, previous_state: 'BaseState'): 15 | return isinstance(previous_state, VerifiedState) and previous_state.document_header.not_empty() 16 | 17 | def acquire(self, document_header: DocumentHeader) -> None: 18 | ... 19 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/acme/verified_state.py: -------------------------------------------------------------------------------- 1 | from contracts.model.document_header import DocumentHeader 2 | from contracts.model.state.straightforward.acme.draft_state import DraftState 3 | from contracts.model.state.straightforward.base_state import BaseState 4 | 5 | 6 | class VerifiedState(BaseState): 7 | __verifier_id: int 8 | 9 | def __init__(self, verifier_id: int): 10 | self.__verifier_id = verifier_id 11 | 12 | def can_change_content(self) -> bool: 13 | return True 14 | 15 | def state_after_content_change(self) -> 'BaseState': 16 | return DraftState() 17 | 18 | def can_change_from(self, previous_state: 'BaseState'): 19 | return ( 20 | isinstance(previous_state, DraftState) 21 | and previous_state.document_header.author_id != self.__verifier_id 22 | and previous_state.document_header.not_empty() 23 | ) 24 | 25 | def acquire(self, document_header: DocumentHeader) -> None: 26 | document_header.verifier_id = self.__verifier_id 27 | -------------------------------------------------------------------------------- /src/main/contracts/model/state/straightforward/base_state.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from contracts.model.content_id import ContentId 4 | from contracts.model.document_header import DocumentHeader 5 | 6 | # TODO introduce an interface 7 | 8 | class BaseState(metaclass=ABCMeta): 9 | _document_header: DocumentHeader 10 | 11 | def init(self, document_header: DocumentHeader): 12 | self._document_header = document_header 13 | document_header.state_descriptor = self.get_state_descriptor() 14 | 15 | def change_content(self, current_content: ContentId) -> 'BaseState': 16 | if self.can_change_content(): 17 | new_state: BaseState = self.state_after_content_change() 18 | new_state.init(self._document_header) 19 | self._document_header.change_current_content(current_content) 20 | new_state.acquire(self._document_header) 21 | return new_state 22 | return self 23 | 24 | @abstractmethod 25 | def can_change_content(self) -> bool: 26 | ... 27 | 28 | @abstractmethod 29 | def state_after_content_change(self) -> 'BaseState': 30 | ... 31 | 32 | def change_state(self, new_state: 'BaseState') -> 'BaseState': 33 | if new_state.can_change_from(self): 34 | new_state.init(self._document_header) 35 | self._document_header.state_descriptor = new_state.get_state_descriptor() 36 | new_state.acquire(self._document_header) 37 | return new_state 38 | 39 | return self 40 | 41 | def get_state_descriptor(self): 42 | return f"{self.__class__.__module__}.{self.__class__.__name__}" 43 | 44 | @abstractmethod 45 | def acquire(self, document_header: DocumentHeader) -> None: 46 | ... 47 | 48 | @abstractmethod 49 | def can_change_from(self, previous_state: 'BaseState') -> bool: 50 | ... 51 | 52 | @property 53 | def document_header(self): 54 | return self._document_header 55 | -------------------------------------------------------------------------------- /src/main/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/core/__init__.py -------------------------------------------------------------------------------- /src/main/core/const.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class Month(enum.IntEnum): 5 | JANUARY = 1 6 | FEBRUARY = 2 7 | MARCH = 3 8 | APRIL = 4 9 | MAY = 5 10 | JUNE = 6 11 | JULY = 7 12 | AUGUST = 8 13 | SEPTEMBER = 9 14 | OCTOBER = 10 15 | NOVEMBER = 11 16 | DECEMBER = 12 17 | -------------------------------------------------------------------------------- /src/main/crm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/crm/__init__.py -------------------------------------------------------------------------------- /src/main/crm/claims/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/crm/claims/__init__.py -------------------------------------------------------------------------------- /src/main/crm/claims/claim_attachment.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from sqlalchemy import Column, DateTime, LargeBinary 5 | from sqlmodel import Field 6 | 7 | from common.base_entity import BaseEntity 8 | from crm.claims.claim import Claim 9 | 10 | 11 | class ClaimAttachment(BaseEntity, table=True): 12 | # @ManyToOne 13 | claim: Optional[Claim] = Field(foreign_key="claim.id") 14 | # @Column(nullable = false) 15 | creation_date: datetime = Field(sa_column=Column(DateTime, nullable=False)) 16 | description: Optional[str] 17 | 18 | # @Lob 19 | # @Column(name = "data", columnDefinition="BLOB") 20 | data: Optional[bytes] = Field(sa_column=Column('data', LargeBinary)) 21 | 22 | def __eq__(self, o): 23 | if not isinstance(o, ClaimAttachment): 24 | return False 25 | return self.id is not None and self.id == o.id 26 | -------------------------------------------------------------------------------- /src/main/crm/claims/claim_attachment_repository.py: -------------------------------------------------------------------------------- 1 | class ClaimAttachmentRepositoryImp: 2 | pass 3 | -------------------------------------------------------------------------------- /src/main/crm/claims/claim_controller.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from fastapi_injector import Injected 4 | 5 | from crm.claims.claim_dto import ClaimDTO 6 | from crm.claims.claim import Claim 7 | from fastapi_utils.cbv import cbv 8 | from fastapi_utils.inferring_router import InferringRouter 9 | from crm.claims.claim_service import ClaimService 10 | from crm.claims.status import Status 11 | 12 | claim_router = InferringRouter(tags=["ClaimController"]) 13 | 14 | @cbv(claim_router) 15 | class ClaimController: 16 | 17 | claim_service: ClaimService = Injected(ClaimService) 18 | 19 | @claim_router.post("/claims/createDraft") 20 | def create(self, claim_dto: ClaimDTO) -> ClaimDTO: 21 | created: Claim = self.claim_service.create(claim_dto) 22 | return self.__to_do(created) 23 | 24 | @claim_router.post("/claims/send") 25 | def send_new(self, claim_dto: ClaimDTO) -> ClaimDTO: 26 | claim_dto.is_draft = False 27 | created: Claim = self.claim_service.create(claim_dto) 28 | return self.__to_do(created) 29 | 30 | @claim_router.post("/claims/{claim_id}/markInProcess") 31 | def mark_as_in_process(self, claim_id: int) -> ClaimDTO: 32 | claim = self.claim_service.set_status(Status.IN_PROCESS, claim_id) 33 | return self.__to_do(claim) 34 | 35 | @claim_router.get("/claims/{claim_id}}") 36 | def try_to_automatically_resolve(self, claim_id: int) -> ClaimDTO: 37 | claim = self.claim_service.try_to_automatically_resolve(claim_id) 38 | return self.__to_do(claim) 39 | 40 | def __to_do(self, claim: Claim) -> ClaimDTO: 41 | return ClaimDTO(**claim.dict()) 42 | -------------------------------------------------------------------------------- /src/main/crm/claims/claim_dto.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Any, Optional 3 | 4 | from crm.claims.claim import Claim 5 | from pydantic import BaseModel 6 | 7 | from crm.claims.status import Status 8 | 9 | 10 | class ClaimDTO(BaseModel): 11 | id: Optional[int] 12 | claim_id: Optional[int] = 0 13 | client_id: Optional[int] = 0 14 | transit_id: Optional[int] = 0 15 | reason: Optional[str] 16 | incident_description: Optional[str] 17 | is_draft: Optional[bool] 18 | creation_date: Optional[datetime] 19 | completion_date: Optional[datetime] 20 | change_date: Optional[datetime] 21 | completion_mode: Optional[Claim.CompletionMode] 22 | status: Optional[Status] 23 | claim_no: Optional[str] 24 | 25 | def __init__(self, **data: Any): 26 | super().__init__(**data) 27 | if data.get("status") == Status.DRAFT: 28 | self.is_draft = True 29 | else: 30 | self.is_draft = False 31 | -------------------------------------------------------------------------------- /src/main/crm/claims/claim_number_generator.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from crm.claims.claim import Claim 4 | from crm.claims.claim_repository import ClaimRepositoryImp 5 | 6 | 7 | class ClaimNumberGenerator: 8 | claim_repository: ClaimRepositoryImp 9 | 10 | @inject 11 | def __init__( 12 | self, 13 | claim_repository: ClaimRepositoryImp, 14 | ): 15 | self.claim_repository = claim_repository 16 | 17 | def generate(self, claim: Claim) -> str: 18 | count = self.claim_repository.count() 19 | prefix = count 20 | if count == 0: 21 | prefix = 1 22 | return str(count) + "---" + claim.creation_date.strftime("%d/%m/%Y") 23 | -------------------------------------------------------------------------------- /src/main/crm/claims/claim_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from crm.claims.claim import Claim 6 | from sqlalchemy import func 7 | from sqlmodel import Session 8 | 9 | 10 | class ClaimRepositoryImp: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def count(self): 18 | results = self.session.query(func.count('*')).select_from(Claim.metadata.tables["cartype"]).scalar() 19 | return results 20 | 21 | def save(self, claim: Claim) -> Optional[Claim]: 22 | self.session.add(claim) 23 | self.session.commit() 24 | self.session.refresh(claim) 25 | return claim 26 | 27 | def get_one(self, claim_id: int) -> Optional[Claim]: 28 | return self.session.query(Claim).filter(Claim.id == claim_id).first() 29 | 30 | def find_all_by_owner_id(self, owner_id: int): 31 | return self.session.query(Claim).filter(Claim.owner_id == owner_id).all() 32 | -------------------------------------------------------------------------------- /src/main/crm/claims/claims_resolver_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | from sqlmodel import Session 5 | 6 | from crm.claims.claims_resolver import ClaimsResolver 7 | 8 | 9 | class ClaimsResolverRepositoryImp: 10 | session: Session 11 | 12 | @inject 13 | def __init__(self, session: Session): 14 | self.session = session 15 | 16 | def save(self, claims_resolver: ClaimsResolver) -> Optional[ClaimsResolver]: 17 | self.session.add(claims_resolver) 18 | self.session.commit() 19 | self.session.refresh(claims_resolver) 20 | return claims_resolver 21 | 22 | def find_by_client_id(self, client_id: int): 23 | return self.session.query(ClaimsResolver).filter(ClaimsResolver.client_id == client_id).first() 24 | -------------------------------------------------------------------------------- /src/main/crm/claims/status.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class Status(enum.Enum): 5 | DRAFT = 1 6 | NEW = 2 7 | IN_PROCESS = 3 8 | REFUNDED = 4 9 | ESCALATED = 5 10 | REJECTED = 6 11 | -------------------------------------------------------------------------------- /src/main/crm/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import enum 4 | from typing import List, Optional 5 | 6 | from sqlalchemy.orm import relationship 7 | 8 | from crm.claims.claim import Claim 9 | from sqlalchemy import Column, Enum 10 | from sqlmodel import Field, Relationship 11 | 12 | from common.base_entity import BaseEntity 13 | 14 | 15 | class Client(BaseEntity, table=True): 16 | __table_args__ = {'extend_existing': True} 17 | 18 | class Type(enum.Enum): 19 | NORMAL = 1 20 | VIP = 2 21 | 22 | class ClientType(enum.Enum): 23 | INDIVIDUAL = 1 24 | COMPANY = 2 25 | 26 | class PaymentType(enum.Enum): 27 | PRE_PAID = 1 28 | POST_PAID = 2 29 | MONTHLY_INVOICE = 3 30 | 31 | type: Optional[Type] = Field(sa_column=Column(Enum(Type))) 32 | name: Optional[str] 33 | last_name: Optional[str] 34 | 35 | default_payment_type: Optional[PaymentType] = Field(sa_column=Column(Enum(PaymentType))) 36 | client_type: Optional[ClientType] = Field(sa_column=Column(Enum(ClientType))) 37 | 38 | def __eq__(self, o): 39 | if not isinstance(o, Client): 40 | return False 41 | return self.id is not None and self.id == o.id 42 | 43 | 44 | __all__ = ["Client"] 45 | -------------------------------------------------------------------------------- /src/main/crm/client_controller.py: -------------------------------------------------------------------------------- 1 | from fastapi_injector import Injected 2 | 3 | from crm.client_dto import ClientDTO 4 | from crm.client import Client 5 | from fastapi_utils.cbv import cbv 6 | from fastapi_utils.inferring_router import InferringRouter 7 | from crm.client_service import ClientService 8 | 9 | client_router = InferringRouter(tags=["ClientController"]) 10 | 11 | @cbv(client_router) 12 | class ClientController: 13 | 14 | client_service: ClientService = Injected(ClientService) 15 | 16 | @client_router.post("/clients") 17 | def register(self, dto: ClientDTO) -> ClientDTO: 18 | c: Client = self.client_service.register_client( 19 | dto.name, dto.last_name, dto.type, dto.default_payment_type) 20 | return self.client_service.load(c.id) 21 | 22 | @client_router.get("/clients/{client_id}") 23 | def find(self, client_id: int) -> ClientDTO: 24 | return self.client_service.load(client_id) 25 | 26 | @client_router.post("/clients/{client_id}/upgrade") 27 | def upgrade_to_vip(self, client_id: int) -> ClientDTO: 28 | self.client_service.upgrade_to_vip(client_id) 29 | return self.client_service.load(client_id) 30 | 31 | @client_router.post("/clients/{client_id}/downgrade") 32 | def downgrade(self, client_id: int) -> ClientDTO: 33 | self.client_service.downgrade_to_regular(client_id) 34 | return self.client_service.load(client_id) 35 | 36 | @client_router.post("/clients/{client_id}/changeDefaultPaymentType") 37 | def change_default_payment_type(self, client_id: int, dto: ClientDTO) -> ClientDTO: 38 | self.client_service.change_default_payment_type(client_id, dto.default_payment_type) 39 | return self.client_service.load(client_id) 40 | -------------------------------------------------------------------------------- /src/main/crm/client_dto.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from crm.client import Client 6 | 7 | 8 | class ClientDTO(BaseModel): 9 | id: Optional[int] 10 | type: Optional[Client.Type] 11 | name: Optional[str] 12 | last_name: Optional[str] 13 | default_payment_type: Optional[Client.PaymentType] 14 | client_type: Optional[Client.ClientType] 15 | number_of_claims: Optional[int] 16 | is_occupied: Optional[bool] 17 | -------------------------------------------------------------------------------- /src/main/crm/client_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from crm.client import Client 6 | from fastapi import Depends 7 | from sqlmodel import Session, select 8 | 9 | 10 | class ClientRepositoryImp: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def get_one(self, client_id: int) -> Optional[Client]: 18 | return self.session.query(Client).where(Client.id == client_id).first() 19 | 20 | def save(self, client: Client) -> Optional[Client]: 21 | self.session.add(client) 22 | self.session.commit() 23 | self.session.refresh(client) 24 | return client 25 | -------------------------------------------------------------------------------- /src/main/crm/notification/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/crm/notification/__init__.py -------------------------------------------------------------------------------- /src/main/crm/notification/client_notification_service.py: -------------------------------------------------------------------------------- 1 | class ClientNotificationService: 2 | def notify_client_about_refund(self, claim_no: str, client_id: int) -> None: 3 | pass 4 | 5 | def ask_for_more_information(self, claim_no: str, client_id: int) -> None: 6 | pass 7 | -------------------------------------------------------------------------------- /src/main/crm/notification/driver_notification_service.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from ride import transit 4 | 5 | 6 | class DriverNotificationService: 7 | def notify_about_possible_transit(self, driver_id: int, transit_id) -> None: 8 | # ... 9 | pass 10 | 11 | def notify_about_changed_transit_address(self, driver_id: int, transit_id) -> None: 12 | # ... 13 | pass 14 | 15 | def notify_about_cancelled_transit(self, driver_id: int, transit_id) -> None: 16 | # ... 17 | pass 18 | 19 | def notify_about_possible_transit(self, driver_id: int, request_id: UUID) -> None: 20 | # find transit and delegate to notify_about_possible_transit(int, int) 21 | pass 22 | 23 | def notify_about_changed_transit_address(self, driver_id: int, request_id: UUID) -> None: 24 | # find transit and delegate to notify_about_changed_transit_address(int, int) 25 | pass 26 | 27 | def notify_about_cancelled_transit(self, driver_id: int, request_id: UUID) -> None: 28 | # find transit and delegate to notify_about_cancelled_transit(int, int) 29 | pass 30 | 31 | def ask_driver_for_details_about_claim(self, claim_no: str, transit_id) -> None: 32 | # ... 33 | pass 34 | -------------------------------------------------------------------------------- /src/main/crm/transitanalyzer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/crm/transitanalyzer/__init__.py -------------------------------------------------------------------------------- /src/main/crm/transitanalyzer/analyzed_addresses_dto.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from geolocation.address.address_dto import AddressDTO 4 | from pydantic import BaseModel 5 | 6 | 7 | class AnalyzedAddressesDTO(BaseModel): 8 | addresses: List[AddressDTO] 9 | -------------------------------------------------------------------------------- /src/main/crm/transitanalyzer/populate_graph_service.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from ride.transit_repository import TransitRepositoryImp 4 | from crm.transitanalyzer.graph_transit_analyzer import GraphTransitAnalyzer 5 | from ride.details.transit_details_dto import TransitDetailsDTO 6 | from ride.details.transit_details_facade import TransitDetailsFacade 7 | 8 | 9 | class PopulateGraphService: 10 | graph_transit_analyzer: GraphTransitAnalyzer 11 | transit_details_facade: TransitDetailsFacade 12 | 13 | @inject 14 | def __init__( 15 | self, 16 | transit_repository: TransitRepositoryImp, 17 | graph_transit_analyzer: GraphTransitAnalyzer, 18 | transit_details_facade: TransitDetailsFacade, 19 | ): 20 | self.transit_repository = transit_repository 21 | self.graph_transit_analyzer = graph_transit_analyzer 22 | self.transit_details_facade = transit_details_facade 23 | 24 | def populate(self): 25 | for transit in self.transit_details_facade.find_completed(): 26 | self.add_to_graph(transit) 27 | 28 | def add_to_graph(self, transit_details: TransitDetailsDTO): 29 | client_id: int = transit_details.client.id 30 | self.graph_transit_analyzer.add_transit_between_addresses( 31 | client_id, 32 | transit_details.transit_id, 33 | int(transit_details.address_from.hash), 34 | int(transit_details.address_to.hash), 35 | transit_details.started, 36 | transit_details.completed_at 37 | ) 38 | -------------------------------------------------------------------------------- /src/main/crm/transitanalyzer/transit_analyzer_controller.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from fastapi_injector import Injected 4 | 5 | from geolocation.address.address_dto import AddressDTO 6 | from crm.transitanalyzer.analyzed_addresses_dto import AnalyzedAddressesDTO 7 | from fastapi_utils.cbv import cbv 8 | from fastapi_utils.inferring_router import InferringRouter 9 | 10 | from geolocation.address.address_repository import AddressRepositoryImp 11 | from crm.transitanalyzer.graph_transit_analyzer import GraphTransitAnalyzer 12 | 13 | transit_analyzer_router = InferringRouter(tags=["TransitAnalyzerController"]) 14 | 15 | @cbv(transit_analyzer_router) 16 | class TransitAnalyzerController: 17 | graph_transit_analyzer: GraphTransitAnalyzer = Injected(GraphTransitAnalyzer) 18 | address_repository: AddressRepositoryImp = Injected(AddressRepositoryImp) 19 | 20 | @transit_analyzer_router.get("/transitAnalyze/{client_id}/{address_id}") 21 | def analyze(self, client_id: int, address_id: int) -> AnalyzedAddressesDTO: 22 | hashes: List[int] = self.graph_transit_analyzer.analyze( 23 | client_id, 24 | self.address_repository.find_hash_by_id(address_id) 25 | ) 26 | address_dtos: List[AddressDTO] = list(map( 27 | lambda a: self.__map_to_address_dto(a), 28 | hashes 29 | )) 30 | return AnalyzedAddressesDTO(addresses=address_dtos) 31 | 32 | def __map_to_address_dto(self, hash: int) -> AddressDTO: 33 | return AddressDTO(self.address_repository.get_by_hash(hash.int)) 34 | -------------------------------------------------------------------------------- /src/main/dev.env.example: -------------------------------------------------------------------------------- 1 | NO_OF_TRANSITS_FOR_CLAIM_AUTOMATIC_REFUND=10 2 | AUTOMATIC_REFUND_FOR_VIP_THRESHOLD=10 3 | MIN_NO_OF_CARS_FOR_ECO_CLASS=3 4 | SQLITE_URL=sqlite:///database.db 5 | -------------------------------------------------------------------------------- /src/main/driverfleet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/driverfleet/__init__.py -------------------------------------------------------------------------------- /src/main/driverfleet/driver_attribute.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | from sqlalchemy import Column, Enum 6 | from sqlalchemy.orm import relationship 7 | from sqlmodel import Field, Relationship, SQLModel 8 | 9 | from driverfleet.driver_attribute_name import DriverAttributeName 10 | 11 | 12 | class DriverAttribute(SQLModel, table=True): 13 | __table_args__ = {'extend_existing': True} 14 | 15 | id: Optional[int] = Field(default=None, primary_key=True) 16 | name: Optional[DriverAttributeName] = Field(sa_column=Column(Enum(DriverAttributeName))) 17 | value: Optional[str] 18 | 19 | # @ManyToOne 20 | # @JoinColumn(name = "DRIVER_ID") 21 | driver_id: Optional[int] = Field(default=None, foreign_key="driver.id") 22 | driver: Optional[Driver] = Relationship( 23 | sa_relationship=relationship( 24 | "driverfleet.driver.Driver", back_populates="attributes") 25 | ) 26 | 27 | def __init__(self, *, driver, name: DriverAttributeName, value: str, **data: Any): 28 | super().__init__(**data) 29 | self.driver = driver 30 | self.driver_id = driver.id 31 | self.name = name 32 | self.value = value 33 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_attribute_dto.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from pydantic import BaseModel 4 | 5 | from driverfleet.driver_attribute import DriverAttribute 6 | from driverfleet.driver_attribute_name import DriverAttributeName 7 | 8 | 9 | class DriverAttributeDTO(BaseModel): 10 | name: Optional[DriverAttributeName] 11 | value: Optional[str] 12 | 13 | def __init__(self, *, driver_attribute: DriverAttribute = None, **data: Any): 14 | super().__init__(**data) 15 | if driver_attribute: 16 | self.name = driver_attribute.name 17 | self.value = driver_attribute.value 18 | 19 | def __eq__(self, o): 20 | if o is None or not isinstance(o, DriverAttributeDTO): 21 | return False 22 | return self.name == o.name and self.value == o.value 23 | 24 | def __hash__(self): 25 | return hash((self.name, self.value)) 26 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_attribute_name.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class DriverAttributeName(enum.Enum): 5 | PENALTY_POINTS = 1 6 | NATIONALITY = 2 7 | YEARS_OF_EXPERIENCE = 3 8 | MEDICAL_EXAMINATION_EXPIRATION_DATE = 4 9 | MEDICAL_EXAMINATION_REMARKS = 5 10 | EMAIL = 6 11 | BIRTHPLACE = 7 12 | COMPANY_NAME = 8 13 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_attribute_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from sqlmodel import Session 6 | 7 | from driverfleet.driver_attribute import DriverAttribute 8 | 9 | 10 | class DriverAttributeRepositoryImp: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def save(self, driver_attribute: DriverAttribute) -> Optional[DriverAttribute]: 18 | self.session.add(driver_attribute) 19 | self.session.commit() 20 | self.session.refresh(driver_attribute) 21 | return driver_attribute 22 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_controller.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from fastapi_injector import Injected 4 | 5 | from driverfleet.driver import Driver 6 | from driverfleet.driver_dto import DriverDTO 7 | from fastapi_utils.cbv import cbv 8 | from fastapi_utils.inferring_router import InferringRouter 9 | from driverfleet.driver_repository import DriverRepositoryImp 10 | from driverfleet.driver_service import DriverService 11 | 12 | driver_router = InferringRouter(tags=["DriverController"]) 13 | 14 | @cbv(driver_router) 15 | class DriverController: 16 | driver_service: DriverService = Injected(DriverService) 17 | driver_repository: DriverRepositoryImp = Injected(DriverRepositoryImp) 18 | 19 | @driver_router.post("/drivers") 20 | def create_driver(self, license: str, first_name: str, last_name: str, photo: Optional[str] = None) -> DriverDTO: 21 | driver = self.driver_service.create_driver( 22 | license, last_name, first_name, Driver.Type.CANDIDATE, Driver.Status.INACTIVE, photo) 23 | 24 | return self.driver_service.load_driver(driver.id) 25 | 26 | @driver_router.get("/drivers/{driver_id}") 27 | def get_driver(self, driver_id: int) -> DriverDTO: 28 | return self.driver_service.load_driver(driver_id) 29 | 30 | @driver_router.post("/drivers/{driver_id}") 31 | def update_driver(self, driver_id: int) -> DriverDTO: 32 | return self.driver_service.load_driver(driver_id) 33 | 34 | @driver_router.post("/drivers/{driver_id}/deactivate") 35 | def deactivate_driver(self, driver_id: int) -> DriverDTO: 36 | self.driver_service.change_driver_status(driver_id, Driver.Status.INACTIVE) 37 | 38 | return self.driver_service.load_driver(driver_id) 39 | 40 | @driver_router.post("/drivers/{driver_id}/activate") 41 | def activate_driver(self, driver_id: int) -> DriverDTO: 42 | self.driver_service.change_driver_status(driver_id, Driver.Status.ACTIVE) 43 | 44 | return self.driver_service.load_driver(driver_id) 45 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_dto.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from pydantic import BaseModel 4 | 5 | from driverfleet.driver import Driver 6 | 7 | 8 | class DriverDTO(BaseModel): 9 | id: Optional[int] 10 | first_name: Optional[str] 11 | last_name: Optional[str] 12 | driver_license: Optional[str] 13 | photo: Optional[str] 14 | status: Optional[Driver.Status] 15 | type: Optional[Driver.Type] 16 | is_occupied: Optional[bool] 17 | 18 | def __init__(self, *, driver: Driver = None, **data: Any): 19 | if driver is not None: 20 | data.update(**driver.dict()) 21 | super().__init__(**data) 22 | if driver is not None and driver.get_driver_license(): 23 | self.driver_license = driver.get_driver_license().as_string() 24 | 25 | def __hash__(self): 26 | return hash((self.id, self.first_name, self.last_name)) 27 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_fee.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import enum 4 | from typing import Optional, Any 5 | 6 | from sqlalchemy import Column, Enum 7 | from sqlalchemy.orm import relationship 8 | from sqlmodel import Field, Relationship 9 | 10 | from common.base_entity import BaseEntity 11 | from money import Money 12 | 13 | 14 | class DriverFee(BaseEntity, table=True): 15 | __table_args__ = {'extend_existing': True} 16 | 17 | class FeeType(enum.Enum): 18 | FLAT = 1 19 | PERCENTAGE = 2 20 | 21 | fee_type: FeeType = Field(sa_column=Column(Enum(FeeType), nullable=False)) 22 | # @OneToOne 23 | driver_id: Optional[int] = Field(default=None, foreign_key="driver.id") 24 | driver: Optional[Driver] = Relationship( 25 | sa_relationship=relationship( 26 | "driverfleet.driver.Driver", back_populates="fee") 27 | ) 28 | amount: Optional[int] 29 | min: Optional[int] 30 | 31 | def __init__(self, **data: Any): 32 | super().__init__(**data) 33 | if "min" in data: 34 | self.set_min(data["min"]) 35 | 36 | def get_min(self) -> Money: 37 | return Money(self.min) 38 | 39 | def set_min(self, min: Money): 40 | self.min = min.value 41 | 42 | def __eq__(self, o): 43 | if not isinstance(o, DriverFee): 44 | return False 45 | return self.id is not None and self.id == o.id 46 | 47 | 48 | __all__ = ["DriverFee"] 49 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_fee_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from sqlmodel import Session 6 | 7 | from driverfleet.driver_fee import DriverFee 8 | 9 | 10 | class DriverFeeRepositoryImp: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def find_by_driver_id(self, driver_id: int) -> Optional[DriverFee]: 18 | return self.session.query(DriverFee).where(DriverFee.driver_id == driver_id).first() 19 | 20 | def save(self, driver_fee: DriverFee) -> Optional[DriverFee]: 21 | self.session.add(driver_fee) 22 | self.session.commit() 23 | self.session.refresh(driver_fee) 24 | return driver_fee 25 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_fee_service.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from driverfleet.driver_fee import DriverFee 6 | from money import Money 7 | from driverfleet.driver_fee_repository import DriverFeeRepositoryImp 8 | 9 | 10 | class DriverFeeService: 11 | driver_fee_repository: DriverFeeRepositoryImp 12 | 13 | @inject 14 | def __init__( 15 | self, 16 | driver_fee_repository: DriverFeeRepositoryImp, 17 | ): 18 | self.driver_fee_repository = driver_fee_repository 19 | 20 | def calculate_driver_fee(self, transit_price: Money, driver_id: int) -> Money: 21 | driver_fee: DriverFee = self.driver_fee_repository.find_by_driver_id(driver_id) 22 | if driver_id is None: 23 | raise AttributeError("driver Fees not defined for driver, driver id = " + str(driver_id)) 24 | final_fee: Optional[Money] = None 25 | if driver_fee.fee_type == DriverFee.FeeType.FLAT: 26 | final_fee = transit_price.subtract(Money(driver_fee.amount)) 27 | else: 28 | final_fee = transit_price.percentage(driver_fee.amount) 29 | 30 | return Money(max(final_fee.to_int(), 0 if not driver_fee.min else driver_fee.min)) 31 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_license.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | class DriverLicense: 5 | DRIVER_LICENSE_REGEX = "^[A-Z9]{5}\\d{6}[A-Z9]{2}\\d[A-Z]{2}$" 6 | 7 | driver_license: str 8 | 9 | def __init__(self, driver_license: str): 10 | self.driver_license = driver_license 11 | 12 | @classmethod 13 | def with_license(cls, driver_license: str) -> 'DriverLicense': 14 | license_regexp = re.compile(cls.DRIVER_LICENSE_REGEX) 15 | if not driver_license or not license_regexp.match(driver_license): 16 | raise ValueError("Illegal license no = " + driver_license) 17 | return cls(driver_license) 18 | 19 | @staticmethod 20 | def without_validation(driver_license: str) -> 'DriverLicense': 21 | return DriverLicense(driver_license) 22 | 23 | def __str__(self) -> str: 24 | return f"DriverLicense{{driverLicense='{self.driver_license}'}}" 25 | 26 | def as_string(self) -> str: 27 | return self.driver_license 28 | -------------------------------------------------------------------------------- /src/main/driverfleet/driver_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List 2 | 3 | from injector import inject 4 | 5 | from sqlmodel import Session 6 | 7 | from driverfleet.driver import Driver 8 | 9 | 10 | class DriverRepositoryImp: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def save(self, driver: Driver) -> Optional[Driver]: 18 | self.session.add(driver) 19 | self.session.commit() 20 | self.session.refresh(driver) 21 | return driver 22 | 23 | def get_one(self, driver_id: int) -> Optional[Driver]: 24 | statement = self.session.query(Driver).where(Driver.id == driver_id) 25 | results = self.session.exec(statement) 26 | return results.scalar_one_or_none() 27 | 28 | def find_all_by_id(self, ids: List[int]) -> List[Driver]: 29 | return self.session.query(Driver).where( 30 | Driver.id.in_(ids) 31 | ).all() 32 | 33 | def find_by_id(self, driver_id) -> Optional[Driver]: 34 | return self.session.query(Driver).where(Driver.id == driver_id).first() 35 | -------------------------------------------------------------------------------- /src/main/driverfleet/driverreport/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/driverfleet/driverreport/__init__.py -------------------------------------------------------------------------------- /src/main/driverfleet/driverreport/driver_report.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Any 2 | 3 | from driverfleet.driver_attribute_dto import DriverAttributeDTO 4 | from driverfleet.driver_attribute_name import DriverAttributeName 5 | from driverfleet.driver_dto import DriverDTO 6 | from tracking.driver_session_dto import DriverSessionDTO 7 | from ride.transit_dto import TransitDTO 8 | from pydantic import BaseModel 9 | 10 | 11 | class DriverReport(BaseModel): 12 | driver_dto: Optional[DriverDTO] 13 | attributes: List[DriverAttributeDTO] 14 | sessions: Dict[DriverSessionDTO, List[TransitDTO]] 15 | 16 | def __init__( 17 | self, 18 | *, 19 | driver_dto: Optional[DriverDTO] = None, 20 | attributes: List[DriverAttributeDTO] = None, 21 | sessions: Dict[DriverSessionDTO, List[TransitDTO]] = None, 22 | **data: Any 23 | ): 24 | attributes = attributes or [] 25 | sessions = sessions or {} 26 | data['attributes'] = attributes 27 | data['sessions'] = sessions 28 | super().__init__(**data) 29 | self.driver_dto = driver_dto 30 | self.attributes = attributes 31 | self.sessions = sessions 32 | 33 | def add_attr(self, name: DriverAttributeName, value: str): 34 | self.attributes.append(DriverAttributeDTO(name=name, value=value)) 35 | 36 | -------------------------------------------------------------------------------- /src/main/driverfleet/driverreport/driver_report_controller.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from fastapi_injector import Injected 4 | 5 | from driverfleet.driverreport.driver_report import DriverReport 6 | from fastapi_utils.cbv import cbv 7 | from fastapi_utils.inferring_router import InferringRouter 8 | from driverfleet.driverreport.sql_based_driver_report_creator import SqlBasedDriverReportCreator 9 | 10 | driver_report_router = InferringRouter(tags=["DriverReportController"]) 11 | 12 | 13 | @cbv(driver_report_router) 14 | class DriverReportController: 15 | driver_report_creator: SqlBasedDriverReportCreator = Injected(SqlBasedDriverReportCreator) 16 | 17 | @driver_report_router.get("/driverreport/{driver_id}") 18 | def load_report_for_driver(self, driver_id: int, last_days: Optional[int] = 0) -> DriverReport: 19 | return self.driver_report_creator.create_report(driver_id, last_days) 20 | -------------------------------------------------------------------------------- /src/main/driverfleet/driverreport/travelleddistance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/driverfleet/driverreport/travelleddistance/__init__.py -------------------------------------------------------------------------------- /src/main/dto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/dto/__init__.py -------------------------------------------------------------------------------- /src/main/geolocation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/geolocation/__init__.py -------------------------------------------------------------------------------- /src/main/geolocation/address/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/geolocation/address/__init__.py -------------------------------------------------------------------------------- /src/main/geolocation/address/address.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from typing import Optional 3 | 4 | from sqlalchemy import Column, String 5 | from sqlmodel import Field 6 | 7 | from common.base_entity import BaseEntity 8 | import logging 9 | logger = logging.getLogger(__name__) 10 | 11 | class Address(BaseEntity, table=True): 12 | __table_args__ = {'extend_existing': True} 13 | country: Optional[str] 14 | district: Optional[str] 15 | city: Optional[str] 16 | street: Optional[str] 17 | building_number: Optional[int] 18 | additional_number: Optional[int] 19 | postal_code: Optional[str] 20 | name: Optional[str] 21 | # @Column(unique=true) 22 | hash: Optional[str] = Field(sa_column=Column("hash", String, unique=True)) 23 | 24 | def __str__(self): 25 | return (f"Address{{" 26 | f"id='{self.id}'" 27 | f", country='{self.country}'" 28 | f", district='{self.district}'" 29 | f", city='{self.city}'" 30 | f", street='{self.street}'" 31 | f", buildingNumber={self.building_number}" 32 | f", additionalNumber={self.additional_number}" 33 | f", postalCode='{self.postal_code}'" 34 | f", name='{self.name}'" 35 | f'}}' 36 | ) 37 | 38 | def gen_hash(self) -> None: 39 | m = hashlib.md5() 40 | for s in ( 41 | self.country, 42 | self.district, 43 | self.city, 44 | self.street, 45 | self.building_number, 46 | self.additional_number, 47 | self.postal_code, 48 | self.name 49 | ): 50 | m.update(str(s).encode('utf-8')) 51 | self.hash = str(int(m.hexdigest(), 16) % 10**16) 52 | 53 | def __eq__(self, o): 54 | if not isinstance(o, Address): 55 | return False 56 | return self.id is not None and self.id == o.id 57 | -------------------------------------------------------------------------------- /src/main/geolocation/address/address_dto.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | 3 | from pydantic import BaseModel 4 | 5 | from geolocation.address.address import Address 6 | 7 | 8 | class AddressDTO(BaseModel): 9 | country: Optional[str] 10 | district: Optional[str] 11 | city: Optional[str] 12 | street: Optional[str] 13 | building_number: Optional[int] 14 | additional_number: Optional[int] 15 | postal_code: Optional[str] 16 | name: Optional[str] 17 | hash: Optional[int] 18 | 19 | def __init__(self, *, address: Optional[Address] = None, **data: Any): 20 | super().__init__(**data) 21 | if address: 22 | self.country = address.country 23 | self.district = address.district 24 | self.city = address.city 25 | self.street = address.street 26 | self.building_number = address.building_number 27 | self.additional_number = address.additional_number 28 | self.postal_code = address.postal_code 29 | self.name = address.name 30 | self.hash = address.hash 31 | 32 | def to_address_entity(self) -> Address: 33 | address = Address() 34 | address.additional_number = self.additional_number 35 | address.building_number = self.building_number 36 | address.city = self.city 37 | address.name = self.name 38 | address.street = self.street 39 | address.country = self.country 40 | address.postal_code = self.postal_code 41 | address.district = self.district 42 | return address 43 | -------------------------------------------------------------------------------- /src/main/geolocation/address/address_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from sqlmodel import Session 6 | 7 | from geolocation.address.address import Address 8 | 9 | 10 | class AddressRepositoryImp: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | # FIX ME: To replace with getOrCreate method instead of that? 18 | # Actual workaround for address uniqueness problem: assign result from repo.save to variable for later usage 19 | def save(self, address: Address) -> Optional[Address]: 20 | address.gen_hash() 21 | 22 | if address.id is None: 23 | existing_address = self.find_by_hash(address.hash) 24 | 25 | if existing_address is not None: 26 | return existing_address 27 | 28 | self.session.add(address) 29 | self.session.commit() 30 | self.session.refresh(address) 31 | return address 32 | 33 | def find_by_hash(self, value: str) -> Optional[Address]: 34 | return self.session.query(Address).where(Address.hash == value).first() 35 | 36 | def find_hash_by_id(self, address_id: int) -> Optional[int]: 37 | return self.session.query(Address).where(Address.id == address_id).first().hash 38 | 39 | def get_by_hash(self, hash: int) -> Address: 40 | return self.session.query(Address).where(Address.hash == hash).first() 41 | 42 | def get_one(self, address_id: int) -> Optional[Address]: 43 | return self.session.query(Address).where(Address.id == address_id).first() 44 | -------------------------------------------------------------------------------- /src/main/geolocation/distance.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import math 3 | from typing import Any, Optional 4 | 5 | from pydantic import BaseModel 6 | 7 | 8 | class Distance(BaseModel): 9 | MILES_TO_KILOMETERS_RATIO = 1.609344 10 | km: float = 0 11 | 12 | @classmethod 13 | @property 14 | def ZERO(cls): 15 | return cls.of_km(0) 16 | 17 | def __init__(self, km: Optional[float] = 0, **data: Any): 18 | super().__init__(**data) 19 | self.km = km 20 | 21 | @staticmethod 22 | def of_km(km: float) -> 'Distance': 23 | return Distance(km) 24 | 25 | def to_km_in_float(self) -> float: 26 | return self.km 27 | 28 | def print_in(self, unit: str) -> str: 29 | if unit == 'km': 30 | if self.km == math.ceil(self.km): 31 | return f"{int(round(self.km))}km" 32 | return "{0:.3f}km".format(self.km) 33 | if unit == 'miles': 34 | km = self.km / self.MILES_TO_KILOMETERS_RATIO 35 | if km == math.ceil(km): 36 | return f"{int(round(km))}miles" 37 | return "{0:.3f}miles".format(km) 38 | 39 | if unit == 'm': 40 | return f"{int(round(self.km * 1000))}m" 41 | raise AttributeError("Invalid unit " + unit) 42 | 43 | def __eq__(self, o): 44 | if o is None or not isinstance(o, Distance): 45 | return False 46 | return self.km == o.km 47 | 48 | def __hash__(self): 49 | m = hashlib.md5() 50 | m.update(str(self.km).encode('utf-8')) 51 | 52 | return int(m.hexdigest(), 16) 53 | 54 | def to_string(self) -> str: 55 | return f"Distance{{km={self.km}}}" 56 | 57 | def __str__(self) -> str: 58 | return self.to_string() 59 | 60 | def add(self, travelled: 'Distance') -> 'Distance': 61 | return Distance.of_km(self.km + travelled.km) 62 | -------------------------------------------------------------------------------- /src/main/geolocation/distance_calculator.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | class DistanceCalculator: 5 | def calculate_by_map( 6 | self, latitude_from: float, longitude_from: float, latitude_to: float, longitude_t: float) -> float: 7 | # ... 8 | 9 | return 42 10 | 11 | def calculate_by_geo(self, latitude_from: float, longitude_from: float, latitude_to: float, longitude_to: float): 12 | # https://www.geeksforgeeks.org/program-distance-two-points-earth/ 13 | # The math module contains a function 14 | # named toRadians which converts from 15 | # degrees to radians. 16 | lon1 = math.radians(longitude_from) 17 | lon2 = math.radians(longitude_to) 18 | lat1 = math.radians(latitude_from) 19 | lat2 = math.radians(latitude_to) 20 | 21 | # Haversine formula 22 | dlon = lon2 - lon1 23 | dlat = lat2 - lat1 24 | a = math.pow(math.sin(dlat / 2), 2) + math.cos(lat1) * math.cos(lat2) * math.pow(math.sin(dlon / 2), 2) 25 | 26 | c = 2 * math.asin(math.sqrt(a)) 27 | 28 | # Radius of earth in kilometers. Use 3956 for miles 29 | r = 6371 30 | 31 | # calculate the result 32 | distance_in_kmeters = c * r 33 | 34 | return distance_in_kmeters 35 | -------------------------------------------------------------------------------- /src/main/geolocation/geocoding_service.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | 4 | class GeocodingService: 5 | def geocode_address(self, address_from) -> List[Optional[float]]: 6 | # TODO...call do zewnętrznego serwisu 7 | 8 | geocoded: List[Optional[float]] = [None, None] 9 | 10 | geocoded[0] = 1.0 # latitude 11 | geocoded[1] = 1.0 # longitude 12 | 13 | return geocoded 14 | -------------------------------------------------------------------------------- /src/main/invocing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/invocing/__init__.py -------------------------------------------------------------------------------- /src/main/invocing/invoice.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Optional 3 | 4 | from common.base_entity import BaseEntity 5 | 6 | 7 | class Invoice(BaseEntity, table=True): 8 | amount: Optional[Decimal] 9 | subject_name: Optional[str] 10 | 11 | def __eq__(self, o): 12 | if not isinstance(o, Invoice): 13 | return False 14 | return self.id is not None and self.id == o.id 15 | -------------------------------------------------------------------------------- /src/main/invocing/invoice_generator.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from injector import inject 4 | 5 | from invocing.invoice import Invoice 6 | from invocing.invoice_repository import InvoiceRepositoryImp 7 | 8 | 9 | class InvoiceGenerator: 10 | invoice_repository: InvoiceRepositoryImp 11 | 12 | @inject 13 | def __init__(self, invoice_repository: InvoiceRepositoryImp): 14 | self.invoice_repository = invoice_repository 15 | 16 | def generate(self, amount: int, subject_name: str): 17 | return self.invoice_repository.save(Invoice(amount=Decimal(amount), subject_name=subject_name)) 18 | -------------------------------------------------------------------------------- /src/main/invocing/invoice_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from invocing.invoice import Invoice 6 | from sqlmodel import Session 7 | 8 | 9 | class InvoiceRepositoryImp: 10 | session: Session 11 | 12 | @inject 13 | def __init__(self, session: Session): 14 | self.session = session 15 | 16 | def save(self, invoice: Invoice) -> Optional[Invoice]: 17 | self.session.add(invoice) 18 | self.session.commit() 19 | self.session.refresh(invoice) 20 | return invoice 21 | -------------------------------------------------------------------------------- /src/main/loyalty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/loyalty/__init__.py -------------------------------------------------------------------------------- /src/main/loyalty/awards_account_controller.py: -------------------------------------------------------------------------------- 1 | from fastapi_injector import Injected 2 | 3 | from loyalty.awards_account_dto import AwardsAccountDTO 4 | from fastapi_utils.cbv import cbv 5 | from fastapi_utils.inferring_router import InferringRouter 6 | from loyalty.awards_service import AwardsService 7 | 8 | awards_account_router = InferringRouter(tags=["AwardsAccountController"]) 9 | 10 | @cbv(awards_account_router) 11 | class AwardsAccountController: 12 | awards_service: AwardsService = Injected(AwardsService) 13 | 14 | @awards_account_router.post("/clients/{client_id}/awards", ) 15 | def create(self, client_id: int) -> AwardsAccountDTO: 16 | self.awards_service.register_to_program(client_id) 17 | return self.awards_service.find_by(client_id) 18 | 19 | @awards_account_router.post("/clients/{client_id}/awards/activate") 20 | def activate(self, client_id: int) -> AwardsAccountDTO: 21 | self.awards_service.activate_account(client_id) 22 | return self.awards_service.find_by(client_id) 23 | 24 | @awards_account_router.post("/clients/{client_id}/awards/deactivate") 25 | def deactivate(self, client_id: int) -> AwardsAccountDTO: 26 | self.awards_service.deactivate_account(client_id) 27 | return self.awards_service.find_by(client_id) 28 | 29 | @awards_account_router.get("/clients/{client_id}/awards/balance") 30 | def calculate_balance(self, client_id: int) -> int: 31 | return self.awards_service.calculate_balance(client_id) 32 | 33 | @awards_account_router.post("/clients/{client_id}/awards/transfer/{to_client_id}/{how_much}") 34 | def transfer_miles(self, client_id: int, to_client_id: int, how_much: int) -> AwardsAccountDTO: 35 | self.awards_service.transfer_miles(client_id, to_client_id, how_much) 36 | return self.awards_service.find_by(client_id) 37 | 38 | @awards_account_router.post("/clients/{client_id}/") 39 | def find_by(self, client_id: int) -> AwardsAccountDTO: 40 | return self.awards_service.find_by(client_id) 41 | -------------------------------------------------------------------------------- /src/main/loyalty/awards_account_dto.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, Any 3 | 4 | from pydantic import BaseModel 5 | 6 | from crm.client_dto import ClientDTO 7 | from loyalty.awards_account import AwardsAccount 8 | 9 | 10 | class AwardsAccountDTO(BaseModel): 11 | client: Optional[ClientDTO] 12 | date: Optional[datetime] 13 | is_active: Optional[bool] 14 | transactions: Optional[int] 15 | 16 | def __init__(self, *, awards_account: AwardsAccount = None, client_dto: ClientDTO = None, **data: Any): 17 | if awards_account is not None: 18 | data.update(**awards_account.dict()) 19 | super().__init__(**data) 20 | if client_dto: 21 | self.client = client_dto 22 | -------------------------------------------------------------------------------- /src/main/loyalty/awards_account_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List 2 | 3 | from injector import inject 4 | 5 | from crm.client import Client 6 | from loyalty.awarded_miles import AwardedMiles 7 | from loyalty.awards_account import AwardsAccount 8 | from sqlmodel import Session 9 | 10 | 11 | class AwardsAccountRepositoryImp: 12 | session: Session 13 | 14 | @inject 15 | def __init__(self, session: Session): 16 | self.session = session 17 | 18 | def find_by_client_id(self, client_id: int) -> AwardsAccount: 19 | statement = self.session.query(AwardsAccount).where(AwardsAccount.client_id == client_id) 20 | results = self.session.exec(statement) 21 | return results.scalar_one_or_none() 22 | 23 | def find_all_miles_by(self, client: Client) -> List[AwardedMiles]: 24 | return self.find_by_client_id(client.id).get_miles() 25 | 26 | def save(self, awards_account: AwardsAccount) -> Optional[AwardsAccount]: 27 | self.session.add(awards_account) 28 | self.session.commit() 29 | self.session.refresh(awards_account) 30 | return awards_account 31 | -------------------------------------------------------------------------------- /src/main/loyalty/awards_service.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from loyalty.awards_account_dto import AwardsAccountDTO 3 | from loyalty.awarded_miles import AwardedMiles 4 | 5 | 6 | class AwardsService(metaclass=abc.ABCMeta): 7 | 8 | def find_by(self, client_id: int) -> AwardsAccountDTO: 9 | raise NotImplementedError 10 | 11 | def register_to_program(self, client_id: int) -> None: 12 | raise NotImplementedError 13 | 14 | def activate_account(self, client_id: int) -> None: 15 | raise NotImplementedError 16 | 17 | def deactivate_account(self, client_id: int) -> None: 18 | raise NotImplementedError 19 | 20 | def register_miles(self, client_id: int, transit_id: int) -> AwardedMiles: 21 | raise NotImplementedError 22 | 23 | def register_non_expiring_miles(self, client_id: int, miles: int) -> AwardedMiles: 24 | raise NotImplementedError 25 | 26 | def remove_miles(self, client_id: int, miles: int) -> None: 27 | raise NotImplementedError 28 | 29 | def calculate_balance(self, client_id: int) -> int: 30 | raise NotImplementedError 31 | 32 | def transfer_miles(self, from_client_id: int, to_client_id: int, miles: int) -> None: 33 | raise NotImplementedError 34 | -------------------------------------------------------------------------------- /src/main/loyalty/constant_until.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from loyalty.miles import Miles 4 | 5 | 6 | class ConstantUntil(Miles): 7 | amount: int 8 | when_expires: datetime 9 | 10 | @staticmethod 11 | def constant_until_forever(amount: int) -> 'ConstantUntil': 12 | return ConstantUntil(amount, datetime.max) 13 | 14 | @staticmethod 15 | def constant_until(amount: int, when_expires: datetime) -> 'ConstantUntil': 16 | return ConstantUntil(amount, when_expires) 17 | 18 | def __init__(self, amount: int, when_expires: datetime): 19 | self.amount = amount 20 | self.when_expires = when_expires 21 | 22 | def get_amount_for(self, moment: datetime) -> int: 23 | return self.amount if self.when_expires.utctimetuple() >= moment.utctimetuple() else 0 24 | 25 | def subtract(self, amount: int, moment: datetime) -> Miles: 26 | if self.get_amount_for(moment) < amount: 27 | raise AttributeError("Insufficient amount of miles") 28 | return ConstantUntil(self.amount - amount, self.when_expires) 29 | 30 | def expires_at(self) -> datetime: 31 | return self.when_expires 32 | 33 | def __eq__(self, other): 34 | if not isinstance(other, ConstantUntil): 35 | return False 36 | return self.amount == other.amount and self.when_expires == other.when_expires 37 | -------------------------------------------------------------------------------- /src/main/loyalty/miles.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from datetime import datetime 3 | 4 | 5 | class Miles(metaclass=abc.ABCMeta): 6 | def get_amount_for(self, moment: datetime) -> int: 7 | raise NotImplementedError 8 | 9 | def subtract(self, amount: int, moment: datetime) -> 'Miles': 10 | raise NotImplementedError 11 | 12 | def expires_at(self) -> datetime: 13 | raise NotImplementedError 14 | -------------------------------------------------------------------------------- /src/main/loyalty/miles_json_mapper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | from loyalty.constant_until import ConstantUntil 5 | from loyalty.miles import Miles 6 | 7 | 8 | class MilesJsonEncoder(json.JSONEncoder): 9 | def default(self, miles: ConstantUntil) -> dict: 10 | if isinstance(miles, Miles): 11 | return { 12 | "amount": miles.amount, 13 | "when_expires": miles.when_expires.isoformat(), 14 | "__class__": "Miles" 15 | } 16 | else: 17 | return super().default(miles) 18 | 19 | 20 | def decode_json_miles(json_obj): 21 | if "__class__" in json_obj and json_obj["__class__"] == "Miles": 22 | return ConstantUntil( 23 | amount=json_obj["amount"], 24 | when_expires=datetime.fromisoformat(json_obj["when_expires"]) 25 | ) 26 | return json_obj 27 | 28 | 29 | class MilesJsonMapper: 30 | @staticmethod 31 | def serialize(miles: Miles) -> str: 32 | return json.dumps(miles, cls=MilesJsonEncoder) 33 | 34 | @staticmethod 35 | def deserialize(json_string: str) -> Miles: 36 | return json.loads(json_string, object_hook=decode_json_miles) 37 | -------------------------------------------------------------------------------- /src/main/loyalty/two_step_expiring_miles.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from loyalty.miles import Miles 4 | 5 | 6 | class TwoStepExpiringMiles(Miles): 7 | amount: int = 0 8 | when_first_half_expires: datetime 9 | when_expires: datetime 10 | 11 | def __init__(self, amount: int, when_first_half_expires: datetime, when_expires: datetime): 12 | self.amount = amount 13 | self.when_first_half_expires = when_first_half_expires 14 | self.when_expires = when_expires 15 | 16 | def get_amount_for(self, moment: datetime) -> int: 17 | if self.when_first_half_expires >= moment: 18 | return self.amount 19 | if self.when_expires >= moment: 20 | return self.amount - self.half_of(self.amount) 21 | return 0 22 | 23 | def half_of(self, amount: int) -> int: 24 | return self.amount // 2 25 | 26 | def subtract(self, amount: int, moment: datetime) -> 'TwoStepExpiringMiles': 27 | current_amount = self.get_amount_for(moment) 28 | if current_amount < amount: 29 | raise AttributeError('Insufficient amount of miles') 30 | return TwoStepExpiringMiles(current_amount - amount, self.when_first_half_expires, self.when_expires) 31 | 32 | def expires_at(self) -> datetime: 33 | return self.when_expires 34 | 35 | def __eq__(self, other): 36 | if not isinstance(other, TwoStepExpiringMiles): 37 | return False 38 | return ( 39 | self.amount == other.amount 40 | and self.when_expires == other.when_expires 41 | and self.when_first_half_expires == other.when_first_half_expires 42 | ) 43 | 44 | def __str__(self): 45 | return ( 46 | f"TwoStepExpiringMiles{{" 47 | f"amount={self.amount}" 48 | f", whenFirstHalfExpires={self.when_first_half_expires}" 49 | f", whenExpires={self.when_expires}" 50 | f"}}" 51 | ) 52 | -------------------------------------------------------------------------------- /src/main/money/__init__.py: -------------------------------------------------------------------------------- 1 | from .money import Money 2 | 3 | __all__ = [ 4 | "Money", 5 | ] 6 | -------------------------------------------------------------------------------- /src/main/money/money.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | class Money: 5 | value: int 6 | 7 | @classmethod 8 | @property 9 | def ZERO(cls): 10 | return cls(0) 11 | 12 | def __init__(self, value: int): 13 | self.value = value 14 | 15 | def add(self, other: 'Money') -> 'Money': 16 | return Money(self.value + other.value) 17 | 18 | def subtract(self, other: 'Money') -> 'Money': 19 | return Money(self.value - other.value) 20 | 21 | def percentage(self, percentage: int) -> 'Money': 22 | return Money(round(percentage * self.value/100.0)) 23 | 24 | def percentage_float(self, percentage: float) -> 'Money': 25 | return Money(int(round(percentage * self.value/100))) 26 | 27 | def to_int(self) -> int: 28 | return self.value 29 | 30 | def __eq__(self, other): 31 | if not isinstance(other, Money): 32 | return False 33 | return self.value == other.value 34 | 35 | def __hash__(self): 36 | m = hashlib.md5() 37 | m.update(str(self.value).encode('utf-8')) 38 | 39 | return int(m.hexdigest(), 16) 40 | 41 | def to_string(self): 42 | value: float = self.value / 100.0 43 | return f'{value:.2f}' 44 | 45 | def __repr__(self) -> str: 46 | return self.to_string() 47 | -------------------------------------------------------------------------------- /src/main/party/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/__init__.py -------------------------------------------------------------------------------- /src/main/party/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/api/__init__.py -------------------------------------------------------------------------------- /src/main/party/api/party_id.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from common.base_entity import new_uuid 4 | 5 | 6 | class PartyId: 7 | id: UUID 8 | 9 | def __init__(self, uuid: UUID = None): 10 | if uuid: 11 | self.id = uuid 12 | else: 13 | self.id = new_uuid() 14 | 15 | def to_uuid(self): 16 | return self.id 17 | -------------------------------------------------------------------------------- /src/main/party/api/party_mapper.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from injector import inject 4 | 5 | from party.api.party_id import PartyId 6 | from party.infra.party_relationship_repository_impl import PartyRelationshipRepositoryImpl 7 | from party.model.party.party_relationship import PartyRelationship 8 | from party.model.party.party_relationship_repository import PartyRelationshipRepository 9 | 10 | 11 | class PartyMapper: 12 | party_relationship_repository: PartyRelationshipRepository 13 | 14 | @inject 15 | def __init__( 16 | self, 17 | party_relationship_repository: PartyRelationshipRepository, 18 | ): 19 | self.party_relationship_repository = party_relationship_repository 20 | 21 | def map_relation(self, party_id: PartyId, relationship_name: str) -> Optional[PartyRelationship]: 22 | return self.party_relationship_repository.find_relationship_for(party_id, relationship_name) 23 | -------------------------------------------------------------------------------- /src/main/party/api/role_object_factory.py: -------------------------------------------------------------------------------- 1 | from typing import Type, Dict 2 | 3 | from party.model.party.party import Party 4 | from party.model.party.party_relationship import PartyRelationship 5 | from party.model.role.party_based_role import PartyBasedRole 6 | 7 | 8 | class RoleObjectFactory: 9 | 10 | roles = Dict[str, PartyBasedRole] 11 | 12 | def __init__(self): 13 | self.roles = {} 14 | 15 | def has_role(self, role): 16 | return role in self.roles 17 | 18 | @classmethod 19 | def from_relationship(cls, party_relationship: PartyRelationship) -> 'RoleObjectFactory': 20 | role_object: 'RoleObjectFactory' = cls() 21 | role_object.add(party_relationship) 22 | return role_object 23 | 24 | def add(self, party_relationship: PartyRelationship) -> None: 25 | self.add_role(party_relationship.role_a, party_relationship.party_a) 26 | self.add_role(party_relationship.role_b, party_relationship.party_b) 27 | 28 | @staticmethod 29 | def format_class_key(clazz): 30 | return f"{clazz.__module__}.{clazz.__name__}" 31 | 32 | def add_role(self, role: str, party: Party): 33 | try: 34 | # in sake of simplicity: a role name is same as a class name with no mapping between them 35 | class_full_path = role.split(".") 36 | cast_class = __import__(class_full_path[0]) 37 | for elem in class_full_path[1:]: 38 | cast_class = getattr(cast_class, elem) 39 | 40 | clazz: Type[PartyBasedRole] = cast_class 41 | instance: PartyBasedRole = clazz(party) 42 | 43 | parent_class = clazz.__base__ 44 | key = self.format_class_key(parent_class) if parent_class != PartyBasedRole else role 45 | self.roles[key] = instance 46 | except Exception as e: 47 | raise AttributeError(e) 48 | 49 | def get_role(self, role): 50 | return self.roles.get(self.format_class_key(role)) 51 | -------------------------------------------------------------------------------- /src/main/party/infra/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/infra/__init__.py -------------------------------------------------------------------------------- /src/main/party/infra/party_repository_impl.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | 3 | from injector import inject 4 | from sqlmodel import Session 5 | 6 | from core.database import get_session 7 | from party.model.party.party import Party 8 | from party.model.party.party_repository import PartyRepository 9 | 10 | 11 | class PartyRepositoryImpl(PartyRepository): 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def put(self, party_id: uuid_pkg.UUID) -> Party: 18 | party: Party = self.session.query(Party).where(Party.id == party_id).one_or_none() 19 | if not party: 20 | party = Party() 21 | party.id = party_id 22 | self.session.add(party) 23 | self.session.commit() 24 | self.session.refresh(party) 25 | 26 | return party 27 | -------------------------------------------------------------------------------- /src/main/party/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/model/__init__.py -------------------------------------------------------------------------------- /src/main/party/model/party/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/model/party/__init__.py -------------------------------------------------------------------------------- /src/main/party/model/party/party.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | 3 | from sqlmodel import Field, SQLModel 4 | 5 | from common.base_entity import new_uuid 6 | 7 | 8 | class Party(SQLModel, table=True): 9 | __table_args__ = {'extend_existing': True} 10 | 11 | id: uuid_pkg.UUID = Field( 12 | default_factory=new_uuid, 13 | nullable=False, 14 | primary_key=True 15 | ) 16 | -------------------------------------------------------------------------------- /src/main/party/model/party/party_relationship.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | from uuid import UUID 3 | from typing import Optional 4 | 5 | from sqlalchemy import Column, Integer, ForeignKey 6 | from sqlalchemy.orm import relationship 7 | from sqlmodel import Field, SQLModel, Relationship 8 | 9 | from common.base_entity import new_uuid 10 | from party.model.party.party import Party 11 | 12 | 13 | class PartyRelationship(SQLModel, table=True): 14 | __table_args__ = {'extend_existing': True} 15 | 16 | id: UUID = Field( 17 | default_factory=new_uuid, 18 | nullable=False, 19 | primary_key=True 20 | ) 21 | 22 | name: Optional[str] 23 | role_a: Optional[str] 24 | role_b: Optional[str] 25 | 26 | # @ManyToOne 27 | party_a_id: Optional[uuid_pkg.UUID] = Field(default=None, foreign_key="party.id") 28 | party_a: Optional[Party] = Relationship( 29 | sa_relationship_kwargs=dict(foreign_keys="[PartyRelationship.party_a_id]") 30 | ) 31 | 32 | # @ManyToOne 33 | party_b_id: Optional[uuid_pkg.UUID] = Field(default=None, foreign_key="party.id") 34 | party_b: Optional[Party] = Relationship( 35 | sa_relationship_kwargs=dict(foreign_keys="[PartyRelationship.party_b_id]") 36 | ) 37 | 38 | -------------------------------------------------------------------------------- /src/main/party/model/party/party_relationship_repository.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Optional 3 | 4 | from party.api.party_id import PartyId 5 | from party.model.party.party import Party 6 | from party.model.party.party_relationship import PartyRelationship 7 | 8 | 9 | class PartyRelationshipRepository(metaclass=abc.ABCMeta): 10 | def put( 11 | self, 12 | party_relationship: str, 13 | party_a_role: str, 14 | party_a: Party, 15 | party_b_role: str, 16 | party_b: Party, 17 | ) -> PartyRelationship: 18 | raise NotImplementedError 19 | 20 | def find_relationship_for(self, party_id: PartyId, relationship_name: str) -> Optional[PartyRelationship]: 21 | raise NotImplementedError 22 | -------------------------------------------------------------------------------- /src/main/party/model/party/party_repository.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import uuid as uuid_pkg 3 | 4 | from party.model.party.party import Party 5 | 6 | 7 | class PartyRepository(metaclass=abc.ABCMeta): 8 | def put(self, pary_id: uuid_pkg.UUID) -> Party: 9 | raise NotImplementedError 10 | -------------------------------------------------------------------------------- /src/main/party/model/party/party_role.py: -------------------------------------------------------------------------------- 1 | import uuid as uuid_pkg 2 | 3 | from sqlmodel import Field, SQLModel 4 | 5 | from common.base_entity import new_uuid 6 | 7 | 8 | class PartyRole(SQLModel, table=True): 9 | __table_args__ = {'extend_existing': True} 10 | 11 | id: uuid_pkg.UUID = Field( 12 | default_factory=new_uuid, 13 | nullable=False, 14 | primary_key=True 15 | ) 16 | 17 | name: str 18 | -------------------------------------------------------------------------------- /src/main/party/model/role/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/model/role/__init__.py -------------------------------------------------------------------------------- /src/main/party/model/role/party_based_role.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from party.model.party.party import Party 4 | 5 | 6 | class PartyBasedRole(metaclass=abc.ABCMeta): 7 | party: Party 8 | 9 | def __init__(self, party: Party): 10 | self.party = party 11 | -------------------------------------------------------------------------------- /src/main/party/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/party/utils/__init__.py -------------------------------------------------------------------------------- /src/main/party/utils/polymorphic_hash_map.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class PolymorphicHashMap: 4 | pass 5 | -------------------------------------------------------------------------------- /src/main/pricing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/pricing/__init__.py -------------------------------------------------------------------------------- /src/main/pricing/tariff.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | from datetime import datetime 3 | from decimal import Decimal, ROUND_HALF_UP 4 | from typing import Optional 5 | 6 | from geolocation.distance import Distance 7 | from money import Money 8 | 9 | 10 | class Tariff: 11 | BASE_FEE: int = 8 12 | km_rate: float 13 | name: str 14 | base_fee: int 15 | 16 | def __init__(self, km_rate: float, name: str, base_fee: int): 17 | self.name = name 18 | self.km_rate = km_rate 19 | self.base_fee = base_fee 20 | 21 | @classmethod 22 | def of_time(cls, time: datetime) -> 'Tariff': 23 | if (time.month == 12 and time.day == 31) or (time.month == 1 and time.day == 1 and time.hour <= 6): 24 | return Tariff(3.50, "Sylwester", cls.BASE_FEE + 3) 25 | else: 26 | # piątek i sobota po 17 do 6 następnego dnia 27 | if ((time.weekday() == calendar.FRIDAY and time.hour >= 17) or 28 | (time.weekday() == calendar.SATURDAY and time.hour <= 6) or 29 | (time.weekday() == calendar.SATURDAY and time.hour >= 17) or 30 | (time.weekday() == calendar.SUNDAY and time.hour <= 6)): 31 | return Tariff(2.5, "Weekend+", cls.BASE_FEE + 2) 32 | else: 33 | # pozostałe godziny weekendu 34 | if (time.weekday() == calendar.SATURDAY and 6 < time.hour < 17) or ( 35 | time.weekday() == calendar.SUNDAY and time.hour > 6): 36 | return Tariff(1.5, "Weekend", cls.BASE_FEE) 37 | else: 38 | # tydzień roboczy 39 | return Tariff(1.0, "Standard", cls.BASE_FEE + 1) 40 | 41 | def calculate_cost(self, distance: Distance) -> Money: 42 | price_big_decimal: Decimal = Decimal( 43 | distance.to_km_in_float() * self.km_rate + self.base_fee 44 | ).quantize(Decimal('.01'), rounding=ROUND_HALF_UP) 45 | final_price: int = int(str(price_big_decimal).replace(".", "")) 46 | return Money(final_price) 47 | -------------------------------------------------------------------------------- /src/main/pricing/tariffs.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from dateutil.tz import tzlocal 4 | 5 | from pricing.tariff import Tariff 6 | 7 | 8 | class Tariffs: 9 | 10 | def choose(self, when: datetime) -> Tariff: 11 | if not when: 12 | when = datetime.now() 13 | return Tariff.of_time(when.astimezone(tzlocal())) 14 | -------------------------------------------------------------------------------- /src/main/repair/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/__init__.py -------------------------------------------------------------------------------- /src/main/repair/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/api/__init__.py -------------------------------------------------------------------------------- /src/main/repair/api/assistance_request.py: -------------------------------------------------------------------------------- 1 | class AssistanceRequest: 2 | pass -------------------------------------------------------------------------------- /src/main/repair/api/repair_request.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | 3 | from party.api.party_id import PartyId 4 | from repair.legacy.parts.parts import Parts 5 | 6 | 7 | class RepairRequest: 8 | vehicle: PartyId 9 | parts_to_repair: Set[Parts] 10 | 11 | def __init__(self, vehicle: PartyId, parts: Set[Parts]): 12 | self.vehicle = vehicle 13 | self.parts_to_repair = parts 14 | -------------------------------------------------------------------------------- /src/main/repair/api/resolve_result.py: -------------------------------------------------------------------------------- 1 | from typing import Set, Optional 2 | from uuid import UUID 3 | 4 | from money import Money 5 | from repair.legacy.parts.parts import Parts 6 | 7 | 8 | class ResolveResult: 9 | class Status: 10 | SUCCESS = 1 11 | ERROR = 2 12 | 13 | handling_party: UUID 14 | total_cost: Optional[Money] 15 | accepted_parts: Optional[Set[Parts]] 16 | status: Optional[Status] 17 | 18 | def __init__( 19 | self, 20 | status: Status, 21 | handling_party: Optional[UUID] = None, 22 | total_cost: Optional[Money] = None, 23 | accepted_parts: Optional[Set[Parts]] = None, 24 | ): 25 | self.status = status 26 | self.handling_party = handling_party 27 | self.total_cost = total_cost 28 | self.accepted_parts = accepted_parts 29 | -------------------------------------------------------------------------------- /src/main/repair/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/legacy/__init__.py -------------------------------------------------------------------------------- /src/main/repair/legacy/dao/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/legacy/dao/__init__.py -------------------------------------------------------------------------------- /src/main/repair/legacy/dao/user_dao.py: -------------------------------------------------------------------------------- 1 | from repair.legacy.parts.parts import Parts 2 | from repair.legacy.user.common_base_abstract_user import CommonBaseAbstractUser 3 | from repair.legacy.user.employee_driver_with_own_car import EmployeeDriverWithOwnCar 4 | from repair.legacy.user.signed_contract import SignedContract 5 | 6 | 7 | class UserDAO: 8 | def get_one(self, user_id: int) -> CommonBaseAbstractUser: 9 | contract: SignedContract = SignedContract() 10 | contract.covered_parts = set(Parts) 11 | contract.coverage_ratio = 100.0 12 | 13 | user: EmployeeDriverWithOwnCar = EmployeeDriverWithOwnCar() 14 | user.contract = contract 15 | return user 16 | -------------------------------------------------------------------------------- /src/main/repair/legacy/job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/legacy/job/__init__.py -------------------------------------------------------------------------------- /src/main/repair/legacy/job/common_base_abstract_job.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class CommonBaseAbstractJob(metaclass=abc.ABCMeta): 5 | pass 6 | -------------------------------------------------------------------------------- /src/main/repair/legacy/job/job_result.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from typing import Dict 3 | 4 | 5 | class JobResult: 6 | class Decision(enum.Enum): 7 | REDIRECTION = 1 8 | ACCEPTED = 2 9 | ERROR = 3 10 | 11 | decision: Decision 12 | params: Dict[str, object] 13 | 14 | def __init__(self, decision: Decision): 15 | self.params = {} 16 | self.decision = decision 17 | 18 | def add_param(self, name: str, value: object): 19 | self.params[name] = value 20 | return self 21 | -------------------------------------------------------------------------------- /src/main/repair/legacy/job/maintenance_job.py: -------------------------------------------------------------------------------- 1 | from repair.legacy.job.common_base_abstract_job import CommonBaseAbstractJob 2 | 3 | 4 | class MaintenanceJob(CommonBaseAbstractJob): 5 | pass 6 | -------------------------------------------------------------------------------- /src/main/repair/legacy/job/repair_job.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | 3 | from money import Money 4 | from repair.legacy.job.common_base_abstract_job import CommonBaseAbstractJob 5 | from repair.legacy.parts.parts import Parts 6 | 7 | 8 | class RepairJob(CommonBaseAbstractJob): 9 | parts_to_repair: Set[Parts] 10 | estimated_value: Money 11 | -------------------------------------------------------------------------------- /src/main/repair/legacy/parts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/legacy/parts/__init__.py -------------------------------------------------------------------------------- /src/main/repair/legacy/parts/parts.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Parts(Enum): 5 | ENGINE = 1 6 | GEARBOX = 2 7 | SUSPENSION = 3 8 | PAINT = 4 9 | -------------------------------------------------------------------------------- /src/main/repair/legacy/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/legacy/service/__init__.py -------------------------------------------------------------------------------- /src/main/repair/legacy/service/job_doer.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from repair.legacy.dao.user_dao import UserDAO 4 | from repair.legacy.job.common_base_abstract_job import CommonBaseAbstractJob 5 | from repair.legacy.user.common_base_abstract_user import CommonBaseAbstractUser 6 | 7 | 8 | class JobDoer: 9 | 10 | user_dao: UserDAO 11 | 12 | @inject 13 | def __init__(self, user_dao: UserDAO): 14 | self.user_dao = user_dao # I'll inject test double some day because it makes total sense to me 15 | 16 | def repair(self, user_id: int, job: CommonBaseAbstractJob): 17 | user: CommonBaseAbstractUser = self.user_dao.get_one(user_id) 18 | return user.do_job(job) 19 | 20 | def repair_2_parallel_models(self, user_id: int, job: CommonBaseAbstractJob): 21 | user: CommonBaseAbstractUser = self.user_dao.get_one(user_id) 22 | return user.do_job(job) 23 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/legacy/user/__init__.py -------------------------------------------------------------------------------- /src/main/repair/legacy/user/common_base_abstract_user.py: -------------------------------------------------------------------------------- 1 | from common.base_entity import BaseEntity 2 | from repair.legacy.job.common_base_abstract_job import CommonBaseAbstractJob 3 | from repair.legacy.job.job_result import JobResult 4 | from repair.legacy.job.maintenance_job import MaintenanceJob 5 | from repair.legacy.job.repair_job import RepairJob 6 | 7 | 8 | class CommonBaseAbstractUser(BaseEntity): 9 | def do_job(self, job: 'CommonBaseAbstractJob') -> JobResult: 10 | # poor man's pattern matching 11 | if isinstance(job, RepairJob): 12 | return self.handle(job) 13 | if isinstance(job, MaintenanceJob): 14 | return self.handle(job) 15 | return self.default_handler(job) 16 | 17 | def handle(self, job: CommonBaseAbstractJob) -> JobResult: 18 | return self.default_handler(job) 19 | 20 | def default_handler(self, job: CommonBaseAbstractJob) -> JobResult: 21 | raise AttributeError(f"{self.__class__.__name__} can not handle {job.__class__.__name__}") 22 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/employee_driver.py: -------------------------------------------------------------------------------- 1 | from repair.legacy.user.common_base_abstract_user import CommonBaseAbstractUser 2 | 3 | 4 | class EmployeeDriver(CommonBaseAbstractUser): 5 | pass 6 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/employee_driver_with_leased_car.py: -------------------------------------------------------------------------------- 1 | from repair.legacy.job.job_result import JobResult 2 | from repair.legacy.job.repair_job import RepairJob 3 | from repair.legacy.user.employee_driver import EmployeeDriver 4 | 5 | 6 | class EmployeeDriverWithLeasedCar(EmployeeDriver): 7 | lasing_company_id: int 8 | 9 | def handle(self, job: RepairJob) -> JobResult: 10 | return JobResult(JobResult.Decision.REDIRECTION).add_param("shouldHandleBy", self.lasing_company_id) 11 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/employee_driver_with_own_car.py: -------------------------------------------------------------------------------- 1 | from typing import Set, Optional 2 | 3 | from sqlalchemy import Column, Integer, ForeignKey 4 | from sqlalchemy.orm import relationship, backref 5 | from sqlmodel import Field, Relationship 6 | 7 | from money import Money 8 | from repair.legacy.job.job_result import JobResult 9 | from repair.legacy.job.repair_job import RepairJob 10 | from repair.legacy.parts.parts import Parts 11 | from repair.legacy.user.employee_driver import EmployeeDriver 12 | from repair.legacy.user.signed_contract import SignedContract 13 | 14 | 15 | class EmployeeDriverWithOwnCar(EmployeeDriver, table=True): 16 | __table_args__ = {'extend_existing': True} 17 | 18 | # @OneToOne 19 | contract_id: Optional[int] = Field(sa_column=Column(Integer, ForeignKey('signedcontract.id'))) 20 | contract: Optional[SignedContract] = Relationship( 21 | sa_relationship=relationship( 22 | "repair.legacy.user.signed_contract.SignedContract", 23 | backref=backref("employeedriverwithowncar", uselist=False) 24 | ) 25 | ) 26 | 27 | # @Column(nullable = false) 28 | 29 | def handle(self, job: RepairJob) -> JobResult: 30 | accepted_parts: Set[Parts] = set(job.parts_to_repair) 31 | accepted_parts.intersection_update(self.contract.covered_parts) 32 | 33 | covered_cost: Money = job.estimated_value.percentage_float(self.contract.coverage_ratio) 34 | total_cost: Money = job.estimated_value.subtract(covered_cost) 35 | 36 | return JobResult( 37 | JobResult.Decision.ACCEPTED 38 | ).add_param( 39 | "totalCost", total_cost 40 | ).add_param( 41 | "acceptedParts", accepted_parts 42 | ) 43 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/signed_contract.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | 3 | from sqlalchemy import Column, JSON 4 | from sqlmodel import Field 5 | 6 | from common.base_entity import BaseEntity 7 | from repair.legacy.parts.parts import Parts 8 | 9 | 10 | class SignedContract(BaseEntity, table=True): 11 | __table_args__ = {'extend_existing': True} 12 | 13 | # @ElementCollection(fetch = FetchType.EAGER) 14 | covered_parts: Set[Parts] = Field(default={}, sa_column=Column(JSON)) 15 | coverage_ratio: float 16 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/subcontractor_driver.py: -------------------------------------------------------------------------------- 1 | from repair.legacy.user.common_base_abstract_user import CommonBaseAbstractUser 2 | 3 | 4 | class SubcontractorDriver(CommonBaseAbstractUser): 5 | pass 6 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/subcontractor_driver_with_own_car.py: -------------------------------------------------------------------------------- 1 | from repair.legacy.user.subcontractor_driver import SubcontractorDriver 2 | 3 | 4 | class SubcontractorDriverWithOwnCar(SubcontractorDriver): 5 | pass 6 | -------------------------------------------------------------------------------- /src/main/repair/legacy/user/subcontractor_with_rented_car.py: -------------------------------------------------------------------------------- 1 | class SubcontractorWithRentedCar: 2 | pass 3 | -------------------------------------------------------------------------------- /src/main/repair/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/model/__init__.py -------------------------------------------------------------------------------- /src/main/repair/model/dict/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/model/dict/__init__.py -------------------------------------------------------------------------------- /src/main/repair/model/dict/party_relationships_dictionary.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | """ 5 | Enum that emulates database dictionary 6 | """ 7 | class PartyRelationshipsDictionary(enum.Enum): 8 | REPAIR = 1 9 | SERVICE = 2 10 | CLEANING = 3 11 | -------------------------------------------------------------------------------- /src/main/repair/model/dict/party_roles_dictionary.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from repair.model.roles.empty.customer import Customer 4 | from repair.model.roles.empty.insured import Insured 5 | from repair.model.roles.repair.extended_insurance import ExtendedInsurance 6 | from repair.model.roles.repair.warranty import Warranty 7 | 8 | 9 | class PartyRolesDictionary(enum.Enum): 10 | INSURER = ExtendedInsurance 11 | INSURED = Insured 12 | GUARANTOR = Warranty 13 | CUSTOMER = Customer 14 | 15 | @property 16 | def role_name(self) -> str: 17 | return self.__name 18 | 19 | def __init__(self, clazz): 20 | self.__name = f"{clazz.__module__}.{clazz.__name__}" 21 | 22 | def get_role_name(self): 23 | return self.role_name 24 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/model/roles/__init__.py -------------------------------------------------------------------------------- /src/main/repair/model/roles/assistance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/model/roles/assistance/__init__.py -------------------------------------------------------------------------------- /src/main/repair/model/roles/assistance/role_for_assistance.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from party.model.party.party import Party 4 | from party.model.role.party_based_role import PartyBasedRole 5 | from repair.api.assistance_request import AssistanceRequest 6 | 7 | 8 | class RoleForAssistance(PartyBasedRole, metaclass=abc.ABCMeta): 9 | def __init__(self, party: Party): 10 | super().__init__(party) 11 | 12 | def handle(self, request: AssistanceRequest): 13 | raise NotImplementedError 14 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/empty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/model/roles/empty/__init__.py -------------------------------------------------------------------------------- /src/main/repair/model/roles/empty/customer.py: -------------------------------------------------------------------------------- 1 | from party.model.party.party import Party 2 | from party.model.role.party_based_role import PartyBasedRole 3 | 4 | 5 | class Customer(PartyBasedRole): 6 | def __init__(self, party: Party): 7 | super().__init__(party) 8 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/empty/insured.py: -------------------------------------------------------------------------------- 1 | from party.model.party.party import Party 2 | from party.model.role.party_based_role import PartyBasedRole 3 | 4 | 5 | class Insured(PartyBasedRole): 6 | def __init__(self, party: Party): 7 | super().__init__(party) 8 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/repair/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/repair/model/roles/repair/__init__.py -------------------------------------------------------------------------------- /src/main/repair/model/roles/repair/extended_insurance.py: -------------------------------------------------------------------------------- 1 | from money import Money 2 | from party.model.party.party import Party 3 | from repair.api.repair_request import RepairRequest 4 | from repair.legacy.parts.parts import Parts 5 | from repair.model.roles.repair.repairing_result import RepairingResult 6 | from repair.model.roles.repair.role_for_repairer import RoleForRepairer 7 | 8 | 9 | class ExtendedInsurance(RoleForRepairer): 10 | 11 | def __init__(self, party: Party): 12 | super().__init__(party) 13 | 14 | def handle(self, repair_request: RepairRequest) -> RepairingResult: 15 | handled_parts = set(repair_request.parts_to_repair) 16 | handled_parts.remove(Parts.PAINT) 17 | 18 | return RepairingResult(self.party.id, Money.ZERO, handled_parts) 19 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/repair/repairing_result.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | from uuid import UUID 3 | 4 | from money import Money 5 | from repair.legacy.parts.parts import Parts 6 | 7 | 8 | class RepairingResult: 9 | handling_party: UUID 10 | total_cost: Money 11 | handled_parts: Set[Parts] 12 | 13 | def __init__(self, handling_party: UUID, total_cost: Money, handled_parts: Set[Parts]): 14 | self.handling_party = handling_party 15 | self.total_cost = total_cost 16 | self.handled_parts = handled_parts 17 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/repair/role_for_repairer.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from party.model.party.party import Party 4 | from party.model.role.party_based_role import PartyBasedRole 5 | from repair.api.repair_request import RepairRequest 6 | 7 | 8 | class RoleForRepairer(PartyBasedRole, metaclass=abc.ABCMeta): 9 | 10 | def __init__(self, party: Party): 11 | super().__init__(party) 12 | 13 | def handle(self, repair_request: RepairRequest): 14 | raise NotImplementedError 15 | -------------------------------------------------------------------------------- /src/main/repair/model/roles/repair/warranty.py: -------------------------------------------------------------------------------- 1 | from money import Money 2 | from party.model.party.party import Party 3 | from repair.api.repair_request import RepairRequest 4 | from repair.model.roles.repair.repairing_result import RepairingResult 5 | from repair.model.roles.repair.role_for_repairer import RoleForRepairer 6 | 7 | 8 | class Warranty(RoleForRepairer): 9 | 10 | def __init__(self, party: Party): 11 | super().__init__(party) 12 | 13 | def handle(self, repair_request: RepairRequest) -> RepairingResult: 14 | handled_parts = set(repair_request.parts_to_repair) 15 | 16 | return RepairingResult(self.party.id, Money.ZERO, handled_parts) 17 | -------------------------------------------------------------------------------- /src/main/ride/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/ride/__init__.py -------------------------------------------------------------------------------- /src/main/ride/change_destination_service.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from uuid import UUID 3 | 4 | from injector import inject 5 | 6 | from geolocation.address.address import Address 7 | from geolocation.distance import Distance 8 | from geolocation.distance_calculator import DistanceCalculator 9 | from geolocation.geocoding_service import GeocodingService 10 | from ride.transit import Transit 11 | from ride.transit_repository import TransitRepositoryImp 12 | 13 | 14 | class ChangeDestinationService: 15 | transit_repository: TransitRepositoryImp 16 | distance_calculator: DistanceCalculator 17 | geocoding_service: GeocodingService 18 | 19 | @inject 20 | def __init__( 21 | self, 22 | transit_repository: TransitRepositoryImp, 23 | distance_calculator: DistanceCalculator, 24 | geocoding_service: GeocodingService, 25 | ): 26 | self.transit_repository = transit_repository 27 | self.distance_calculator = distance_calculator 28 | self.geocoding_service = geocoding_service 29 | 30 | def change_transit_address_to(self, request_uuid: UUID, new_address: Address, address_from: Address) -> Distance: 31 | # FIXME later: add some exceptions handling 32 | geo_from: List[float] = self.geocoding_service.geocode_address(address_from) 33 | geo_to: List[float] = self.geocoding_service.geocode_address(new_address) 34 | 35 | new_distance = Distance.of_km(float( 36 | self.distance_calculator.calculate_by_map(geo_from[0], geo_from[1], geo_to[0], geo_to[1]) 37 | )) 38 | transit: Transit = self.transit_repository.find_by_transit_request_uuid(request_uuid) 39 | if transit: 40 | transit.change_destination(new_distance) 41 | return new_distance 42 | -------------------------------------------------------------------------------- /src/main/ride/demand_service.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from injector import inject 4 | 5 | from ride.transit_demand import TransitDemand 6 | from ride.transit_demand_repository import TransitDemandRepository 7 | 8 | 9 | class DemandService: 10 | transit_demand_repository: TransitDemandRepository 11 | 12 | @inject 13 | def __init__( 14 | self, 15 | transit_demand_repository: TransitDemandRepository, 16 | ): 17 | self.transit_demand_repository = transit_demand_repository 18 | 19 | def publish_demand(self, request_uuid: UUID) -> None: 20 | self.transit_demand_repository.save(TransitDemand(request_uuid)) 21 | 22 | def cancel_demand(self, request_uuid: UUID) -> None: 23 | transit_demand: TransitDemand = self.transit_demand_repository.find_by_transit_request_uuid(request_uuid) 24 | if transit_demand: 25 | transit_demand.cancel() 26 | 27 | def accept_demand(self, request_uuid: UUID) -> None: 28 | transit_demand: TransitDemand = self.transit_demand_repository.find_by_transit_request_uuid(request_uuid) 29 | if transit_demand: 30 | transit_demand.accept() 31 | 32 | def exists_for(self, request_uuid: UUID) -> bool: 33 | return self.transit_demand_repository.find_by_transit_request_uuid(request_uuid) is not None 34 | -------------------------------------------------------------------------------- /src/main/ride/details/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/ride/details/__init__.py -------------------------------------------------------------------------------- /src/main/ride/details/status.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class Status(enum.Enum): 5 | DRAFT = 1 6 | CANCELLED = 2 7 | WAITING_FOR_DRIVER_ASSIGNMENT = 3 8 | DRIVER_ASSIGNMENT_FAILED = 4 9 | TRANSIT_TO_PASSENGER = 5 10 | IN_TRANSIT = 6 11 | COMPLETED = 7 12 | -------------------------------------------------------------------------------- /src/main/ride/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/ride/events/__init__.py -------------------------------------------------------------------------------- /src/main/ride/events/transit_completed.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from common.event import Event 3 | 4 | 5 | class TransitCompleted(Event): 6 | client_id: int 7 | transitId: int 8 | address_from_hash: int 9 | address_to_hash: int 10 | started: datetime 11 | complete_at: datetime 12 | event_timestamp: datetime 13 | 14 | def __init__( 15 | self, 16 | client_id: int, 17 | transit_id: int, 18 | address_from_hash: int, 19 | address_to_hash: int, 20 | started: datetime, 21 | complete_at: datetime, 22 | event_timestamp: datetime 23 | ): 24 | self.client_id = client_id 25 | self.transit_id = transit_id 26 | self.address_from_hash = address_from_hash 27 | self.address_to_hash = address_to_hash 28 | self.started = started 29 | self.complete_at = complete_at 30 | self.event_timestamp = event_timestamp 31 | -------------------------------------------------------------------------------- /src/main/ride/request_for_transit.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from uuid import UUID 3 | 4 | from sqlmodel import Field 5 | 6 | from common.base_entity import BaseEntity, new_uuid 7 | from geolocation.distance import Distance 8 | from money import Money 9 | from pricing.tariff import Tariff 10 | from sqlalchemy.orm import composite 11 | 12 | 13 | class RequestForTransit(BaseEntity, table=True): 14 | __table_args__ = {'extend_existing': True} 15 | 16 | request_uuid: UUID = Field( 17 | default_factory=new_uuid, 18 | nullable=False, 19 | ) 20 | 21 | tariff_km_rate: Optional[float] = 0 22 | tariff_name: Optional[str] = None 23 | tariff_base_fee: Optional[int] = 0 24 | __tariff: Tariff = composite(Tariff, 'tariff_km_rate', 'tariff_name', 'tariff_base_fee') 25 | 26 | distance: float 27 | __distance: Distance = composite(Distance, 'distance') 28 | 29 | class Config: 30 | arbitrary_types_allowed = True 31 | 32 | def __init__( 33 | self, 34 | *, 35 | tariff: Tariff, 36 | distance: Distance, 37 | **data: Any 38 | ): 39 | super().__init__(**data) 40 | self.set_tariff(tariff) 41 | self.distance = distance.to_km_in_float() 42 | 43 | def get_estimated_price(self) -> Money: 44 | tariff = self.get_tariff() 45 | distance = self.get_distance() 46 | return tariff.calculate_cost(distance) 47 | 48 | def set_tariff(self, tariff: Tariff): 49 | self.tariff_km_rate = tariff.km_rate 50 | self.tariff_name = tariff.name 51 | self.tariff_base_fee = tariff.base_fee 52 | 53 | def get_tariff(self) -> Tariff: 54 | return self.__tariff 55 | 56 | def get_distance(self) -> Distance: 57 | return self.__distance 58 | -------------------------------------------------------------------------------- /src/main/ride/request_for_transit_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from uuid import UUID 3 | 4 | from injector import inject 5 | from sqlmodel import Session 6 | 7 | from ride.request_for_transit import RequestForTransit 8 | 9 | 10 | class RequestForTransitRepository: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def find_by_request_uuid(self, request_uuid: UUID) -> Optional[RequestForTransit]: 18 | return self.session.query(RequestForTransit).where(RequestForTransit.request_uuid == request_uuid).first() 19 | 20 | def save(self, request_for_transit: RequestForTransit) -> Optional[RequestForTransit]: 21 | self.session.add(request_for_transit) 22 | self.session.commit() 23 | self.session.refresh(request_for_transit) 24 | return request_for_transit 25 | 26 | def get_one(self, request_id: int) -> Optional[RequestForTransit]: 27 | return self.session.query(RequestForTransit).where(RequestForTransit.id == request_id).first() 28 | -------------------------------------------------------------------------------- /src/main/ride/start_transit_service.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from injector import inject 4 | 5 | from ride.request_transit_service import RequestTransitService 6 | from ride.transit import Transit 7 | from ride.transit_repository import TransitRepositoryImp 8 | 9 | 10 | class StartTransitService: 11 | transit_repository: TransitRepositoryImp 12 | request_transit_service: RequestTransitService 13 | 14 | @inject 15 | def __init__( 16 | self, 17 | transit_repository: TransitRepositoryImp, 18 | request_transit_service: RequestTransitService, 19 | ): 20 | self.transit_repository = transit_repository 21 | self.request_transit_service = request_transit_service 22 | 23 | def start(self, request_uuid: UUID) -> Transit: 24 | transit: Transit = Transit( 25 | tariff=self.request_transit_service.find_tariff(request_uuid), 26 | transit_request_uuid=request_uuid 27 | ) 28 | return self.transit_repository.save(transit) 29 | -------------------------------------------------------------------------------- /src/main/ride/transit_demand.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from typing import Any 3 | from uuid import UUID 4 | 5 | from sqlalchemy import Column, Enum 6 | from sqlmodel import Field 7 | 8 | from common.base_entity import BaseEntity 9 | 10 | 11 | class TransitDemand(BaseEntity, table=True): 12 | __table_args__ = {'extend_existing': True} 13 | 14 | transit_request_uuid: UUID 15 | 16 | class Status(enum.Enum): 17 | CANCELLED = 1 18 | WAITING_FOR_DRIVER_ASSIGNMENT = 2 19 | TRANSIT_TO_PASSENGER = 3 20 | 21 | status: Status = Field(sa_column=Column(Enum(Status))) 22 | pickup_address_change_counter: int = 0 23 | 24 | def __init__(self, transit_request_uuid: UUID, **data: Any): 25 | super().__init__(**data) 26 | self.transit_request_uuid = transit_request_uuid 27 | self.status = self.Status.WAITING_FOR_DRIVER_ASSIGNMENT 28 | 29 | def change_pickup(self, distance_from_previous_pickup: float): 30 | if distance_from_previous_pickup > 0.25: 31 | raise AttributeError(f"Address 'from' cannot be changed, id = {self.id}") 32 | elif self.status != self.Status.WAITING_FOR_DRIVER_ASSIGNMENT: 33 | raise AttributeError(f"Address 'from' cannot be changed, id = {self.id}") 34 | elif self.pickup_address_change_counter > 2: 35 | raise AttributeError(f"Address 'from' cannot be changed, id = {self.id}") 36 | self.pickup_address_change_counter = self.pickup_address_change_counter + 1 37 | 38 | def accept(self): 39 | self.status = self.Status.TRANSIT_TO_PASSENGER 40 | 41 | def cancel(self): 42 | if self.status != self.Status.WAITING_FOR_DRIVER_ASSIGNMENT: 43 | raise AttributeError(f"Demand cannot be cancelled, id = {self.id}") 44 | self.status = self.Status.CANCELLED 45 | -------------------------------------------------------------------------------- /src/main/ride/transit_demand_repository.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from uuid import UUID 3 | 4 | from injector import inject 5 | from sqlmodel import Session 6 | 7 | from ride.transit_demand import TransitDemand 8 | 9 | 10 | class TransitDemandRepository: 11 | session: Session 12 | 13 | @inject 14 | def __init__(self, session: Session): 15 | self.session = session 16 | 17 | def find_by_transit_request_uuid(self, request_uuid: UUID) -> TransitDemand: 18 | return self.session.query(TransitDemand).where(TransitDemand.transit_request_uuid == request_uuid).first() 19 | 20 | def save(self, transit_demand: TransitDemand) -> Optional[TransitDemand]: 21 | self.session.add(transit_demand) 22 | self.session.commit() 23 | self.session.refresh(transit_demand) 24 | return transit_demand 25 | -------------------------------------------------------------------------------- /src/main/ride/transit_repository.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional 3 | from uuid import UUID 4 | 5 | from injector import inject 6 | from sqlalchemy import desc, text 7 | 8 | from crm.client import Client 9 | from driverfleet.driver import Driver 10 | from sqlmodel import Session 11 | 12 | from geolocation.address.address import Address 13 | from ride.details.status import Status 14 | from ride.transit import Transit 15 | 16 | 17 | class TransitRepositoryImp: 18 | session: Session 19 | 20 | @inject 21 | def __init__(self, session: Session): 22 | self.session = session 23 | 24 | def get_one(self, transit_id: int) -> Optional[Transit]: 25 | return self.session.query(Transit).where(Transit.id == transit_id).first() 26 | 27 | def find_by_transit_request_uuid(self, transit_request_uuid: UUID) -> Optional[Transit]: 28 | return self.session.query(Transit).where(Transit.transit_request_uuid == transit_request_uuid).first() 29 | 30 | def find_by_client_id(self, client_id: int) -> List[Transit]: 31 | stmt = text( 32 | "select T.* from transit AS T join transitdetails AS TD " 33 | "ON T.id = TD.transit_id where TD.client_id = :client" 34 | ) 35 | return self.session.query(Transit).from_statement(stmt).params( 36 | client=client_id 37 | ).all() 38 | 39 | def save(self, transit: Transit) -> Optional[Transit]: 40 | self.session.add(transit) 41 | self.session.commit() 42 | self.session.refresh(transit) 43 | return transit 44 | -------------------------------------------------------------------------------- /src/main/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/__init__.py -------------------------------------------------------------------------------- /src/main/tests/agreements/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/agreements/__init__.py -------------------------------------------------------------------------------- /src/main/tests/assignment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/assignment/__init__.py -------------------------------------------------------------------------------- /src/main/tests/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/common/__init__.py -------------------------------------------------------------------------------- /src/main/tests/common/address_fixture.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | 3 | from injector import inject 4 | from mockito import arg_that, when 5 | 6 | from geolocation.address.address import Address 7 | from geolocation.address.address_dto import AddressDTO 8 | from geolocation.address.address_repository import AddressRepositoryImp 9 | from geolocation.geocoding_service import GeocodingService 10 | from tests.common.address_matcher import AddressMatcher 11 | 12 | 13 | class AddressFixture: 14 | 15 | address_repository: AddressRepositoryImp 16 | 17 | @inject 18 | def __init__( 19 | self, 20 | address_repository: AddressRepositoryImp, 21 | ): 22 | self.address_repository = address_repository 23 | 24 | def an_address(self) -> Address: 25 | return self.address_repository.save( 26 | Address(country="Polska", city="Warszawa", street="Młynarska", building_number=20)) 27 | 28 | def an_address_mock( 29 | self, 30 | geocoding_service: GeocodingService, 31 | country: str, 32 | city: str, 33 | street: str, 34 | building_number: int 35 | ) -> AddressDTO: 36 | address_dto: AddressDTO = AddressDTO(country=country, city=city, street=street, building_number=building_number) 37 | 38 | latitude: float = random() 39 | longitude: float = random() 40 | 41 | when(geocoding_service).geocode_address( 42 | arg_that(AddressMatcher(dto=address_dto).matches) 43 | ).thenReturn([latitude, longitude]) 44 | 45 | return address_dto 46 | -------------------------------------------------------------------------------- /src/main/tests/common/address_matcher.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from mockito.matchers import Matcher 4 | 5 | from geolocation.address.address import Address 6 | from geolocation.address.address_dto import AddressDTO 7 | import logging 8 | logger = logging.getLogger(__name__) 9 | 10 | class AddressMatcher(Matcher): 11 | country: str 12 | city: str 13 | street: str 14 | building_number: int 15 | 16 | def __init__( 17 | self, 18 | address: Optional[Address] = None, 19 | dto: Optional[AddressDTO] = None, 20 | country: Optional[str] = None, 21 | city: Optional[str] = None, 22 | street: Optional[str] = None, 23 | building_number: Optional[int] = None 24 | ): 25 | if dto: 26 | address = dto.to_address_entity() 27 | if address: 28 | self.country = address.country 29 | self.city = address.city 30 | self.street = address.street 31 | self.building_number = address.building_number 32 | else: 33 | self.country = country 34 | self.city = city 35 | self.street = street 36 | self.building_number = building_number 37 | 38 | def matches(self, right: Address) -> bool: 39 | if not right: 40 | return False 41 | return ( 42 | self.country == right.country 43 | and self.city == right.city 44 | and self.street == right.street 45 | and self.building_number == right.building_number 46 | ) 47 | 48 | def __repr__(self): 49 | return ( 50 | f"country: {self.country}, " 51 | f"city: {self.city}, " 52 | f"street: {self.street}, " 53 | f"building_number: {self.building_number}") 54 | -------------------------------------------------------------------------------- /src/main/tests/common/awards_account_fixture.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from crm.client import Client 4 | from loyalty.awards_service import AwardsService 5 | 6 | 7 | class AwardsAccountFixture: 8 | awards_service: AwardsService 9 | 10 | @inject 11 | def __init__(self, awards_service: AwardsService): 12 | self.awards_service = awards_service 13 | 14 | def awards_account(self, client: Client): 15 | self.awards_service.register_to_program(client.id) 16 | 17 | def active_awards_account(self, client: Client): 18 | self.awards_account(client) 19 | self.awards_service.activate_account(client.id) 20 | -------------------------------------------------------------------------------- /src/main/tests/common/car_type_fixture.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from carfleet.car_class import CarClass 4 | from carfleet.car_type_dto import CarTypeDTO 5 | from carfleet.car_type_service import CarTypeService 6 | 7 | 8 | class CarTypeFixture: 9 | 10 | car_type_service: CarTypeService 11 | 12 | @inject 13 | def __init__(self, car_type_service: CarTypeService): 14 | self.car_type_service = car_type_service 15 | 16 | def an_active_car_category(self, car_class: CarClass) -> CarTypeDTO: 17 | car_type_dto: CarTypeDTO = CarTypeDTO() 18 | car_type_dto.car_class = car_class 19 | car_type_dto.description = "opis" 20 | car_type: CarTypeDTO = self.car_type_service.create(car_type_dto) 21 | for _ in range(1, car_type.min_no_of_cars_to_activate_class + 1): 22 | self.car_type_service.register_car(car_type.car_class) 23 | self.car_type_service.activate(car_type.id) 24 | return car_type 25 | -------------------------------------------------------------------------------- /src/main/tests/common/claim_fixture.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from crm.claims.claim import Claim 4 | from crm.claims.claim_dto import ClaimDTO 5 | from ride.transit_dto import TransitDTO 6 | from crm.client import Client 7 | from crm.claims.claim_service import ClaimService 8 | from ride.transit import Transit 9 | from tests.common.client_fixture import ClientFixture 10 | 11 | 12 | class ClaimFixture: 13 | claim_service: ClaimService 14 | client_fixture: ClientFixture 15 | 16 | @inject 17 | def __init__( 18 | self, 19 | claim_service: ClaimService, 20 | client_fixture: ClientFixture, 21 | ): 22 | self.claim_service = claim_service 23 | self.client_fixture = client_fixture 24 | 25 | def create_claim_default_reason(self, client: Client, transit: Transit) -> Claim: 26 | claim_dto: ClaimDTO = self.claim_dto("Okradli mnie na hajs", "$$$", client.id, transit.id) 27 | claim_dto.is_draft = False 28 | claim: Claim = self.claim_service.create(claim_dto) 29 | return claim 30 | 31 | def create_claim(self, client: Client, transit: TransitDTO, reason: str) -> Claim: 32 | claim_dto: ClaimDTO = self.claim_dto("Okradli mnie na hajs", reason, client.id, transit.id) 33 | claim_dto.is_draft = False 34 | return self.claim_service.create(claim_dto) 35 | 36 | def create_and_resolve_claim(self, client: Client, transit: Transit) -> Claim: 37 | claim: Claim = self.create_claim_default_reason(client, transit) 38 | claim = self.claim_service.try_to_automatically_resolve(claim.id) 39 | return claim 40 | 41 | def claim_dto(self, desc: str, reason: str, client_id: int, transit_id: int) -> ClaimDTO: 42 | claim_dto: ClaimDTO = ClaimDTO() 43 | claim_dto.client_id = client_id 44 | claim_dto.transit_id = transit_id 45 | claim_dto.incident_description = desc 46 | claim_dto.reason = reason 47 | return claim_dto 48 | -------------------------------------------------------------------------------- /src/main/tests/common/client_fixture.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | 3 | from crm.client import Client 4 | from crm.client_repository import ClientRepositoryImp 5 | 6 | 7 | class ClientFixture: 8 | client_repository: ClientRepositoryImp 9 | 10 | @inject 11 | def __init__( 12 | self, 13 | client_repository: ClientRepositoryImp, 14 | ): 15 | self.client_repository = client_repository 16 | 17 | def a_client_default(self) -> Client: 18 | return self.client_repository.save(Client()) 19 | 20 | def a_client(self, client_type: Client.Type) -> Client: 21 | client: Client = Client() 22 | client.type = client_type 23 | return self.client_repository.save(client) 24 | -------------------------------------------------------------------------------- /src/main/tests/common/stubbed_transit_price.py: -------------------------------------------------------------------------------- 1 | from injector import inject 2 | from mockito import when 3 | 4 | from money import Money 5 | from pricing.tariff import Tariff 6 | from pricing.tariffs import Tariffs 7 | from ride.transit import Transit 8 | 9 | 10 | class StubbedTransitPrice: 11 | tariffs: Tariffs 12 | 13 | @inject 14 | def __init__(self, tariffs: Tariffs): 15 | self.tariffs = tariffs 16 | 17 | def stub(self, faked: Money) -> Transit: 18 | fake_tariff: Tariff = Tariff(0, "fake", faked) 19 | # when(self.tariffs).choose(isA(Instant.class)).thenReturn(fake_tariff) 20 | 21 | -------------------------------------------------------------------------------- /src/main/tests/contracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/application/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/application/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/application/dynamic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/application/dynamic/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/application/dynamic/document_operation_result_assert.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from contracts.application.acme.dynamic.document_operation_result import DocumentOperationResult 4 | from contracts.model.content_id import ContentId 5 | 6 | 7 | class DocumentOperationResultAssert(TestCase): 8 | __result: DocumentOperationResult 9 | 10 | def __init__(self, result: DocumentOperationResult): 11 | super().__init__() 12 | self.__result = result 13 | 14 | def editable(self): 15 | self.assertTrue(self.__result.is_content_change_possible()) 16 | return self 17 | 18 | def uneditable(self) -> 'DocumentOperationResultAssert': 19 | self.assertFalse(self.__result.is_content_change_possible()) 20 | return self 21 | 22 | def state(self, state: str) -> 'DocumentOperationResultAssert': 23 | self.assertEqual(state, self.__result.state_name) 24 | return self 25 | 26 | def content(self, content_id: ContentId) -> 'DocumentOperationResultAssert': 27 | self.assertEqual(content_id, self.__result.content_id) 28 | return self 29 | 30 | def possibleNextStates(self, *states: str) -> 'DocumentOperationResultAssert': 31 | self.assertEqual( 32 | set(states), 33 | self.__result.possible_transitions_and_rules.keys() 34 | ) 35 | return self 36 | -------------------------------------------------------------------------------- /src/main/tests/contracts/application/straightforward/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/application/straightforward/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/application/straightforward/acme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/application/straightforward/acme/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/application/straightforward/acme/contract_result_assert.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from contracts.application.acme.straigthforward.contract_result import ContractResult 4 | from contracts.model.state.straightforward.base_state import BaseState 5 | 6 | 7 | class ContractResultAssert(TestCase): 8 | __result: ContractResult 9 | 10 | def __init__(self, result: ContractResult): 11 | super().__init__() 12 | self.__result = result 13 | self.assertEqual(ContractResult.Result.SUCCESS, result.result) 14 | 15 | def state(self, state: BaseState) -> 'ContractResultAssert': 16 | self.assertEqual(state.get_state_descriptor(), self.__result.state_descriptor) 17 | return self 18 | -------------------------------------------------------------------------------- /src/main/tests/contracts/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/legacy/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/legacy/test_document.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from contracts.legacy.document import Document 4 | from contracts.legacy.document_status import DocumentStatus 5 | from contracts.legacy.user import User 6 | from core.database import create_db_and_tables, drop_db_and_tables 7 | 8 | class TestDocument(TestCase): 9 | 10 | ANY_NUMBER: str = "number" 11 | ANY_USER: User = User(id=1) 12 | OTHER_USER: User = User(id=2) 13 | TITLE: str = "title" 14 | 15 | def setUp(self): 16 | create_db_and_tables() 17 | 18 | def test_only_draft_can_be_verified_by_user_other_than_creator(self): 19 | doc: Document = Document(self.ANY_NUMBER, self.ANY_USER) 20 | 21 | doc.verify_by(self.OTHER_USER) 22 | 23 | self.assertEqual(DocumentStatus.VERIFIED, doc.status) 24 | 25 | def test_can_not_change_published(self): 26 | doc: Document = Document(self.ANY_NUMBER, self.ANY_USER) 27 | doc.change_title(self.TITLE) 28 | doc.verify_by(self.OTHER_USER) 29 | doc.publish() 30 | 31 | try: 32 | doc.change_title("") 33 | except AttributeError: 34 | self.assertTrue(True) 35 | self.assertEqual(self.TITLE, doc.title) 36 | 37 | def test_changing_verified_moves_to_draft(self): 38 | doc: Document = Document(self.ANY_NUMBER, self.ANY_USER) 39 | doc.change_title(self.TITLE) 40 | doc.verify_by(self.OTHER_USER) 41 | 42 | doc.change_title("") 43 | 44 | self.assertEqual(DocumentStatus.DRAFT, doc.status) 45 | 46 | def tearDown(self) -> None: 47 | drop_db_and_tables() 48 | -------------------------------------------------------------------------------- /src/main/tests/contracts/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/model/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/model/state/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/model/state/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/model/state/dynamic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/model/state/dynamic/__init__.py -------------------------------------------------------------------------------- /src/main/tests/contracts/model/state/dynamic/fake_document_publisher.py: -------------------------------------------------------------------------------- 1 | from typing import Set, Any, Type 2 | from unittest import TestCase 3 | 4 | from common.application_event_publisher import ApplicationEventPublisher 5 | from contracts.model.state.dynamic.config.events.document_event import DocumentEvent 6 | 7 | 8 | class FakeDocumentPublisher(ApplicationEventPublisher, TestCase): 9 | events: Set[Any] 10 | 11 | def __init__(self): 12 | super().__init__() 13 | self.events = set() 14 | 15 | def publish_event_object(self, event: Any): 16 | self.events.add(event) 17 | 18 | def contains(self, event: Type[DocumentEvent]): 19 | found: bool = any(map(lambda e: isinstance(e, event), self.events)) 20 | self.assertTrue(found) 21 | 22 | def no_events(self): 23 | self.assertEqual(0, len(self.events)) 24 | 25 | def reset(self): 26 | self.events = set() 27 | -------------------------------------------------------------------------------- /src/main/tests/contracts/model/state/straightforward/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/contracts/model/state/straightforward/__init__.py -------------------------------------------------------------------------------- /src/main/tests/crm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/crm/__init__.py -------------------------------------------------------------------------------- /src/main/tests/crm/claims/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/crm/claims/__init__.py -------------------------------------------------------------------------------- /src/main/tests/distance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/distance/__init__.py -------------------------------------------------------------------------------- /src/main/tests/driverfleet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/driverfleet/__init__.py -------------------------------------------------------------------------------- /src/main/tests/driverfleet/driverreport/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/driverfleet/driverreport/__init__.py -------------------------------------------------------------------------------- /src/main/tests/driverfleet/driverreport/travelleddistance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/driverfleet/driverreport/travelleddistance/__init__.py -------------------------------------------------------------------------------- /src/main/tests/driverfleet/test_driver_license.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from driverfleet.driver_license import DriverLicense 4 | 5 | 6 | class TestDriverLicense(TestCase): 7 | 8 | def test_cannot_create_invalid_license(self): 9 | with self.assertRaises(ValueError): 10 | DriverLicense.with_license("invalid") 11 | with self.assertRaises(ValueError): 12 | DriverLicense.with_license("") 13 | 14 | def test_can_create_valid_license(self): 15 | # when 16 | license: DriverLicense = DriverLicense.with_license("FARME100165AB5EW") 17 | 18 | # then 19 | self.assertEqual("FARME100165AB5EW", license.as_string()) 20 | 21 | def test_can_create_invalid_license_explicitly(self): 22 | # when 23 | license: DriverLicense = DriverLicense.without_validation("invalid") 24 | 25 | # then 26 | self.assertEqual("invalid", license.as_string()) 27 | -------------------------------------------------------------------------------- /src/main/tests/entity/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/entity/__init__.py -------------------------------------------------------------------------------- /src/main/tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/integration/__init__.py -------------------------------------------------------------------------------- /src/main/tests/integration/test_driver_tracking_service_integration.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from unittest import TestCase 3 | 4 | import pytz 5 | from dateutil.relativedelta import relativedelta 6 | from freezegun import freeze_time 7 | 8 | from core.database import create_db_and_tables, drop_db_and_tables 9 | from geolocation.distance import Distance 10 | from driverfleet.driver import Driver 11 | from tracking.driver_tracking_service import DriverTrackingService 12 | from tests.common.fixtures import DependencyResolver, Fixtures 13 | 14 | dependency_resolver = DependencyResolver() 15 | 16 | 17 | class TestDriverTrackingServiceIntegration(TestCase): 18 | NOON = datetime(1989, 12, 12, 12, 12).astimezone(pytz.utc) 19 | NOON_FIVE = NOON + relativedelta(minutes=5) 20 | 21 | driver_tracking_service: DriverTrackingService = dependency_resolver.resolve_dependency( 22 | DriverTrackingService 23 | ) 24 | 25 | fixtures: Fixtures = dependency_resolver.resolve_dependency(Fixtures) 26 | 27 | def setUp(self): 28 | create_db_and_tables() 29 | 30 | def test_can_calculate_travelled_distance_from_short_transit(self): 31 | # given 32 | driver: Driver = self.fixtures.an_active_regular_driver() 33 | # and 34 | with freeze_time(self.NOON): 35 | # and 36 | self.driver_tracking_service.register_position(driver.id, 53.32055555555556, -1.7297222222222221, self.NOON) 37 | self.driver_tracking_service.register_position(driver.id, 53.31861111111111, -1.6997222222222223, self.NOON) 38 | self.driver_tracking_service.register_position(driver.id, 53.32055555555556, -1.7297222222222221, self.NOON) 39 | 40 | # when 41 | distance: Distance = self.driver_tracking_service.calculate_travelled_distance( 42 | driver.id, self.NOON, self.NOON_FIVE) 43 | 44 | # then 45 | self.assertEqual("4.009km", distance.print_in("km")) 46 | 47 | def tearDown(self) -> None: 48 | drop_db_and_tables() 49 | -------------------------------------------------------------------------------- /src/main/tests/integration/test_graph_transit_analyzer_integration.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List 3 | from unittest import TestCase 4 | 5 | from crm.transitanalyzer.graph_transit_analyzer import GraphTransitAnalyzer 6 | from tests.common.fixtures import DependencyResolver 7 | 8 | dependency_resolver = DependencyResolver() 9 | 10 | 11 | class TestGraphTransitAnalyzerIntegration(TestCase): 12 | 13 | analyzer: GraphTransitAnalyzer = dependency_resolver.resolve_dependency(GraphTransitAnalyzer) 14 | 15 | def test_can_recognize_new_address(self): 16 | # given 17 | self.analyzer.add_transit_between_addresses(1, 1, 111, 222, datetime.now(), datetime.now()) 18 | self.analyzer.add_transit_between_addresses(1, 1, 222, 333, datetime.now(), datetime.now()) 19 | self.analyzer.add_transit_between_addresses(1, 1, 333, 444, datetime.now(), datetime.now()) 20 | 21 | # when 22 | results: List[int] = self.analyzer.analyze(1, 111) 23 | 24 | # then 25 | [self.assertIn(result, (111, 222, 333, 444)) for result in results] 26 | -------------------------------------------------------------------------------- /src/main/tests/money/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/money/__init__.py -------------------------------------------------------------------------------- /src/main/tests/money/test_money.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from money import Money 4 | 5 | 6 | class TestMoney(TestCase): 7 | 8 | def test_can_create_money_from_integer(self): 9 | # expect 10 | self.assertEqual("100.00", Money(10000).to_string()) 11 | self.assertEqual("0.00", Money(0).to_string()) 12 | self.assertEqual("10.12", Money(1012).to_string()) 13 | 14 | def test_should_project_money_to_integer(self): 15 | # expect 16 | self.assertEqual(10, Money(10).to_int()) 17 | self.assertEqual(0, Money(0).to_int()) 18 | self.assertEqual(-5, Money(-5).to_int()) 19 | 20 | def test_can_add_money(self): 21 | # expect 22 | self.assertEqual(Money(1000), Money(500).add(Money(500))) 23 | self.assertEqual(Money(1042), Money(1020).add(Money(22))) 24 | self.assertEqual(Money(0), Money(0).add(Money(0))) 25 | self.assertEqual(Money(-2), Money(-4).add(Money(2))) 26 | 27 | def test_can_subtract_money(self): 28 | # expect 29 | self.assertEqual(Money.ZERO, Money(50).subtract(Money(50))) 30 | self.assertEqual(Money(998), Money(1020).subtract(Money(22))) 31 | self.assertEqual(Money(-1), Money(2).subtract(Money(3))) 32 | 33 | def test_can_calculate_percentage(self): 34 | # expect 35 | self.assertEqual("30.00", Money(10000).percentage(30).to_string()) 36 | self.assertEqual("26.40", Money(8800).percentage(30).to_string()) 37 | self.assertEqual("88.00", Money(8800).percentage(100).to_string()) 38 | self.assertEqual("0.00", Money(8800).percentage(0).to_string()) 39 | self.assertEqual("13.20", Money(4400).percentage(30).to_string()) 40 | self.assertEqual("0.30", Money(100).percentage(30).to_string()) 41 | self.assertEqual("0.00", Money(1).percentage(40).to_string()) 42 | -------------------------------------------------------------------------------- /src/main/tests/pricing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/pricing/__init__.py -------------------------------------------------------------------------------- /src/main/tests/repair/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/repair/__init__.py -------------------------------------------------------------------------------- /src/main/tests/repair/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/repair/api/__init__.py -------------------------------------------------------------------------------- /src/main/tests/repair/api/vehicle_repair_assert.py: -------------------------------------------------------------------------------- 1 | from typing import Set, List 2 | from unittest import TestCase 3 | 4 | from money import Money 5 | from party.api.party_id import PartyId 6 | from repair.api.resolve_result import ResolveResult 7 | from repair.legacy.parts.parts import Parts 8 | 9 | 10 | class VehicleRepairAssert(TestCase): 11 | result: ResolveResult 12 | 13 | def __init__(self, result: ResolveResult, demand_success: bool = True): 14 | super().__init__() 15 | self.result = result 16 | if demand_success: 17 | self.assertEqual(ResolveResult.Status.SUCCESS, result.status) 18 | else: 19 | self.assertEqual(ResolveResult.Status.ERROR, result.status) 20 | 21 | def free(self) -> 'VehicleRepairAssert': 22 | self.assertEqual(Money.ZERO, self.result.total_cost) 23 | return self 24 | 25 | def all_parts(self, parts: Set[Parts]) -> 'VehicleRepairAssert': 26 | self.assertEqual(parts, self.result.accepted_parts) 27 | return self 28 | 29 | def by(self, handling_party: PartyId): 30 | self.assertEqual(handling_party.to_uuid(), self.result.handling_party) 31 | return self 32 | 33 | def all_parts_but(self, parts: Set[Parts], excluded_parts: List[Parts]): 34 | exptected_parts: Set[Parts] = set(parts) 35 | exptected_parts.difference_update(excluded_parts) 36 | 37 | self.assertEqual(exptected_parts, self.result.accepted_parts) 38 | return self 39 | -------------------------------------------------------------------------------- /src/main/tests/repair/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/repair/legacy/__init__.py -------------------------------------------------------------------------------- /src/main/tests/repair/legacy/job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/repair/legacy/job/__init__.py -------------------------------------------------------------------------------- /src/main/tests/repair/legacy/job/test_repair.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | from unittest import TestCase 3 | 4 | from money import Money 5 | from repair.legacy.job.job_result import JobResult 6 | from repair.legacy.job.repair_job import RepairJob 7 | from repair.legacy.parts.parts import Parts 8 | from repair.legacy.user.employee_driver_with_own_car import EmployeeDriverWithOwnCar 9 | from repair.legacy.user.signed_contract import SignedContract 10 | 11 | 12 | class TestRepair(TestCase): 13 | 14 | def test_employee_driver_with_own_car_covered_by_warranty_should_repair_for_free(self): 15 | # given 16 | employee: EmployeeDriverWithOwnCar = EmployeeDriverWithOwnCar() 17 | employee.contract = self.full_coverage_warranty() 18 | # when 19 | result: JobResult = employee.do_job(self.full_repair()) 20 | # then 21 | self.assertEqual(JobResult.Decision.ACCEPTED, result.decision) 22 | self.assertEqual(Money.ZERO, result.params.get("totalCost")) 23 | self.assertEqual(self.all_parts(), result.params.get("acceptedParts")) 24 | 25 | def full_repair(self): 26 | job: RepairJob = RepairJob() 27 | job.estimated_value = Money(50000) 28 | job.parts_to_repair = self.all_parts() 29 | return job 30 | 31 | def full_coverage_warranty(self) -> SignedContract: 32 | contract: SignedContract = SignedContract() 33 | contract.coverage_ratio = 100.0 34 | contract.covered_parts = self.all_parts() 35 | return contract 36 | 37 | def all_parts(self) -> Set[Parts]: 38 | return set(Parts) 39 | -------------------------------------------------------------------------------- /src/main/tests/repair/legacy/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/repair/legacy/service/__init__.py -------------------------------------------------------------------------------- /src/main/tests/repair/legacy/service/test_job_doer.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | from unittest import TestCase 3 | 4 | from fastapi.params import Depends 5 | 6 | from core.database import create_db_and_tables, drop_db_and_tables 7 | from money import Money 8 | from repair.legacy.job.job_result import JobResult 9 | from repair.legacy.job.repair_job import RepairJob 10 | from repair.legacy.parts.parts import Parts 11 | from repair.legacy.service.job_doer import JobDoer 12 | from tests.common.fixtures import DependencyResolver 13 | 14 | dependency_resolver = DependencyResolver() 15 | 16 | 17 | class TestJobDoer(TestCase): 18 | 19 | ANY_USER: int = 1 20 | 21 | job_doer: JobDoer = dependency_resolver.resolve_dependency( 22 | JobDoer 23 | ) 24 | 25 | def setUp(self): 26 | create_db_and_tables() 27 | 28 | def test_employee_with_own_car_with_warranty_should_have_covered_all_parts_for_free(self): 29 | result: JobResult = self.job_doer.repair(self.ANY_USER, self.repair_job()) 30 | 31 | self.assertEqual(result.decision, JobResult.Decision.ACCEPTED) 32 | self.assertEqual(result.params.get("acceptedParts"), self.all_parts()) 33 | self.assertEqual(result.params.get("totalCost"), Money.ZERO) 34 | 35 | def repair_job(self) -> RepairJob: 36 | job: RepairJob = RepairJob() 37 | job.parts_to_repair = self.all_parts() 38 | job.estimated_value = Money(7000) 39 | return job 40 | 41 | def all_parts(self) -> Set[Parts]: 42 | return set(Parts) 43 | 44 | def tearDown(self) -> None: 45 | drop_db_and_tables() 46 | -------------------------------------------------------------------------------- /src/main/tests/ride/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/ride/__init__.py -------------------------------------------------------------------------------- /src/main/tests/ride/test_request_for_transit.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from unittest import TestCase 3 | 4 | from geolocation.distance import Distance 5 | from pricing.tariff import Tariff 6 | from ride.request_for_transit import RequestForTransit 7 | 8 | 9 | class TestRequestForTransit(TestCase): 10 | def test_can_create_request_for_transit(self): 11 | # when 12 | request_for_transit: RequestForTransit = self.request_transit() 13 | 14 | # expect 15 | self.assertIsNotNone(request_for_transit.get_tariff()) 16 | self.assertNotEquals(0, request_for_transit.get_tariff().km_rate) 17 | 18 | def request_transit(self) -> RequestForTransit: 19 | tariff: Tariff = Tariff.of_time(datetime.now()) 20 | return RequestForTransit(tariff=tariff, distance=Distance.ZERO) 21 | -------------------------------------------------------------------------------- /src/main/tests/ride/test_transit.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from unittest import TestCase 3 | 4 | from common.base_entity import new_uuid 5 | from geolocation.distance import Distance 6 | from pricing.tariff import Tariff 7 | from ride.transit import Transit 8 | 9 | 10 | class TestTransit(TestCase): 11 | 12 | def test_can_change_transit_destination(self): 13 | # given 14 | transit: Transit = self.transit() 15 | 16 | # expect 17 | transit.change_destination(Distance.of_km(20)) 18 | 19 | # then 20 | self.assertEqual(Distance.of_km(20), transit.get_distance()) 21 | 22 | def test_cannot_change_destination_when_transit_is_completed(self): 23 | # given 24 | transit: Transit = self.transit() 25 | # and 26 | transit.complete_ride_at(Distance.of_km(20)) 27 | 28 | # expect 29 | with self.assertRaises(AttributeError): 30 | transit.change_destination(Distance.of_km(20)) 31 | 32 | def test_can_complete_transit(self): 33 | transit: Transit = self.transit() 34 | # and 35 | transit.complete_ride_at(Distance.of_km(20)) 36 | 37 | # then 38 | self.assertEqual(Transit.Status.COMPLETED, transit.status) 39 | 40 | def transit(self) -> Transit: 41 | return Transit(tariff=Tariff.of_time(datetime.now()), transit_request_uuid=new_uuid()) 42 | -------------------------------------------------------------------------------- /src/main/tests/test_app.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from starlette.testclient import TestClient 4 | 5 | from cabs_application import CabsApplication 6 | 7 | 8 | class TestApp(TestCase): 9 | def setUp(self) -> None: 10 | self.client = TestClient(CabsApplication().create_app()) 11 | 12 | def test_read_main(self): 13 | response = self.client.get("/transits/0") 14 | self.assertEqual(response.status_code, 200) 15 | self.assertEqual(response.json(), {"msg": "Hello World"}) 16 | -------------------------------------------------------------------------------- /src/main/tests/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tests/ui/__init__.py -------------------------------------------------------------------------------- /src/main/tracking/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragarthPl/cabs-python/424c4b7a8e487cb63b12e5ee73ceb90344e93286/src/main/tracking/__init__.py -------------------------------------------------------------------------------- /src/main/tracking/driver_position.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, Any 3 | 4 | from common.base_entity import BaseEntity 5 | from driverfleet.driver import Driver 6 | from sqlalchemy import Column, DateTime, Float, Integer 7 | from sqlmodel import Field 8 | 9 | 10 | class DriverPosition(BaseEntity, table=True): 11 | # @ManyToOne 12 | driver_id: Optional[int] = Field(default=0, sa_column=Column(Integer, nullable=True)) 13 | # @Column(nullable = false) 14 | latitude: Optional[float] = Field(sa_column=Column(Float, nullable=False)) 15 | # @Column(nullable = false) 16 | longitude: Optional[float] = Field(sa_column=Column(Float, nullable=False)) 17 | # @Column(nullable = false) 18 | seen_at: Optional[datetime] = Field(sa_column=Column(DateTime, nullable=False)) 19 | 20 | def __eq__(self, o): 21 | if not isinstance(o, DriverPosition): 22 | return False 23 | return self.id is not None and self.id == o.id 24 | -------------------------------------------------------------------------------- /src/main/tracking/driver_position_dto.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class DriverPositionDTO(BaseModel): 8 | driver_id: Optional[int] 9 | latitude: Optional[float] 10 | longitude: Optional[float] 11 | seen_at: Optional[datetime] 12 | -------------------------------------------------------------------------------- /src/main/tracking/driver_position_dtov_2.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, Any 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class DriverPositionDTOV2(BaseModel): 8 | driver_id: Optional[int] 9 | latitude: Optional[float] 10 | longitude: Optional[float] 11 | seen_at: Optional[datetime] 12 | 13 | def __init__(self, driver_id: Optional[int], latitude: Optional[float], longitude: Optional[float], 14 | seen_at: Optional[datetime], **data: Any): 15 | super().__init__(**data) 16 | self.driver_id = driver_id 17 | self.latitude = latitude 18 | self.longitude = longitude 19 | self.seen_at = seen_at 20 | -------------------------------------------------------------------------------- /src/main/tracking/driver_session.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from carfleet.car_class import CarClass 5 | from common.base_entity import BaseEntity 6 | from driverfleet.driver import Driver 7 | from sqlalchemy import Column, DateTime, Enum, String, Integer 8 | from sqlalchemy.orm import relationship 9 | from sqlmodel import Field, Relationship 10 | 11 | 12 | class DriverSession(BaseEntity, table=True): 13 | # @Column(nullable = false) 14 | logged_at: datetime = Field(sa_column=Column(DateTime, nullable=False)) 15 | logged_out_at: Optional[datetime] 16 | # @ManyToOne 17 | driver_id: Optional[int] = Field(default=0, sa_column=Column(Integer, nullable=True)) 18 | 19 | # @Column(nullable = false) 20 | plates_number: str = Field(sa_column=Column(String, nullable=False)) 21 | # @Enumerated(EnumType.STRING) 22 | car_class: Optional[CarClass] = Field(sa_column=Column(Enum(CarClass))) 23 | car_brand: Optional[str] 24 | 25 | def __eq__(self, o): 26 | if not isinstance(o, DriverSession): 27 | return False 28 | return self.id is not None and self.id == o.id 29 | -------------------------------------------------------------------------------- /src/main/tracking/driver_session_controller.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from fastapi_injector import Injected 4 | 5 | from tracking.driver_session_dto import DriverSessionDTO 6 | from fastapi_utils.cbv import cbv 7 | from fastapi_utils.inferring_router import InferringRouter 8 | from tracking.driver_session_service import DriverSessionService 9 | 10 | driver_session_router = InferringRouter(tags=["DriverSessionController"]) 11 | 12 | @cbv(driver_session_router) 13 | class DriverSessionController: 14 | driver_session_service: DriverSessionService = Injected(DriverSessionService) 15 | 16 | @driver_session_router.post("/drivers/{driver_id}/driver_sessions/login") 17 | def log_in(self, driver_id: int, dto: DriverSessionDTO): 18 | self.driver_session_service.log_in(driver_id, dto.plates_number, dto.car_class, dto.car_brand) 19 | return {} 20 | 21 | @driver_session_router.delete("/drivers/{driver_id}/driver_sessions/{session_id}") 22 | def log_out(self, driver_id: int, session_id: int): 23 | self.driver_session_service.log_out(session_id) 24 | return {} 25 | 26 | @driver_session_router.delete("/drivers/{driver_id}/driver_sessions/") 27 | def log_out_current(self, driver_id: int): 28 | self.driver_session_service.log_out_current_session(driver_id) 29 | return {} 30 | 31 | @driver_session_router.get("/drivers/{driver_id}/driver_sessions/login") 32 | def list(self, driver_id: int) -> List[DriverSessionDTO]: 33 | return list( 34 | map( 35 | lambda s: DriverSessionDTO(**s.dict()), 36 | self.driver_session_service.find_by_driver(driver_id) 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /src/main/tracking/driver_session_dto.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | from carfleet.car_class import CarClass 6 | from pydantic import BaseModel 7 | 8 | 9 | class DriverSessionDTO(BaseModel): 10 | logged_at: Optional[datetime] 11 | logged_out_at: Optional[datetime] 12 | plates_number: Optional[str] 13 | car_class: Optional[CarClass] 14 | car_brand: Optional[str] 15 | 16 | def __hash__(self): 17 | m = hashlib.md5() 18 | for s in ( 19 | self.logged_at, 20 | self.logged_out_at, 21 | self.plates_number, 22 | self.car_class, 23 | self.car_brand, 24 | ): 25 | m.update(str(s).encode('utf-8')) 26 | return int(m.hexdigest(), 16) 27 | -------------------------------------------------------------------------------- /src/main/tracking/driver_tracking_controller.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from fastapi_injector import Injected 4 | 5 | from tracking.driver_position_dto import DriverPositionDTO 6 | from tracking.driver_position import DriverPosition 7 | from fastapi_utils.cbv import cbv 8 | from fastapi_utils.inferring_router import InferringRouter 9 | from tracking.driver_tracking_service import DriverTrackingService 10 | 11 | driver_tracking_router = InferringRouter(tags=["DriverTrackingController"]) 12 | 13 | @cbv(driver_tracking_router) 14 | class DriverTrackingController: 15 | tracking_service: DriverTrackingService = Injected(DriverTrackingService) 16 | 17 | @driver_tracking_router.post("/driverPositions/") 18 | def create(self, driver_position_dto: DriverPositionDTO) -> DriverPositionDTO: 19 | driver_position = self.tracking_service.register_position( 20 | driver_position_dto.driver_id, 21 | driver_position_dto.latitude, 22 | driver_position_dto.longitude, 23 | driver_position_dto.seen_at 24 | ) 25 | 26 | return self.__to_dto(driver_position) 27 | 28 | @driver_tracking_router.get("/driverPositions/{driver_id}/total") 29 | def create(self, driver_id: int, from_position: datetime, to_position: datetime) -> float: 30 | return self.tracking_service.calculate_travelled_distance( 31 | driver_id, from_position, to_position 32 | ).to_km_in_float() 33 | 34 | def __to_dto(self, driver_position: DriverPosition): 35 | dto = DriverPositionDTO() 36 | dto.driver_id = driver_position.driver_id 37 | dto.latitude = driver_position.latitude 38 | dto.longitude = driver_position.longitude 39 | dto.seen_at = driver_position.seen_at 40 | return dto 41 | --------------------------------------------------------------------------------