felicity-lims/felicity/api/gql/billing/mutations.py

395 lines
14 KiB
Python
Raw Normal View History

2023-12-05 22:53:43 +08:00
import logging
from datetime import datetime
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
import strawberry # noqa
from strawberry.types import Info # noqa
2023-12-04 18:04:30 +08:00
2024-07-27 03:00:08 +08:00
from felicity.api.gql.auth import auth_from_info
2024-02-16 23:48:19 +08:00
from felicity.api.gql.billing.types import (AnalysisDiscountType,
AnalysisPriceType,
ProfileDiscountType,
ProfilePriceType,
TestBillTransactionType,
TestBillType, VoucherCodeType,
VoucherType)
from felicity.api.gql.permissions import IsAuthenticated
from felicity.api.gql.types import OperationError
2024-07-27 05:29:01 +08:00
from felicity.apps.billing import schemas, utils
2024-07-24 04:30:01 +08:00
from felicity.apps.billing.enum import DiscountType, TransactionKind
2024-02-16 23:48:19 +08:00
from felicity.apps.billing.schemas import (TestBillTransactionUpdate,
TestBillUpdate)
2024-07-28 03:52:31 +08:00
from felicity.apps.billing.services import (AnalysisDiscountService,
AnalysisPriceService,
ProfileDiscountService,
ProfilePriceService,
TestBillService,
TestBillTransactionService,
VoucherCodeService, VoucherService)
2024-06-28 02:16:12 +08:00
from felicity.apps.impress.invoicing import utils as invoice_utils
2023-12-04 18:04:30 +08:00
2023-12-05 22:53:43 +08:00
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
2023-12-04 18:04:30 +08:00
2023-12-05 22:53:43 +08:00
ProfilePriceResponse = strawberry.union(
"ProfilePriceResponse", (ProfilePriceType, OperationError), description="" # noqa
)
AnalysisPriceResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"AnalysisPriceResponse", (AnalysisPriceType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
ProfileDiscountResponse = strawberry.union(
"ProfileDiscountResponse",
(ProfileDiscountType, OperationError),
description="", # noqa
2023-12-05 22:53:43 +08:00
)
AnalysisDiscountResponse = strawberry.union(
"AnalysisDiscountResponse",
(AnalysisDiscountType, OperationError),
description="", #
2023-12-05 22:53:43 +08:00
)
VoucherResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"VoucherResponse", (VoucherType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
VoucherCodeResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"VoucherCodeResponse", (VoucherCodeType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
TestBillTransactionResponse = strawberry.union(
"TestBillTransactionResponse",
(TestBillTransactionType, OperationError),
description="", #
2023-12-05 22:53:43 +08:00
)
TestBillResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"TestBillResponse", (TestBillType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
@strawberry.input
class PriceInput:
amount: float
is_active: bool | None = True
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class PriceDiscountInput:
discount_type: str
2023-12-20 02:01:46 +08:00
value_type: str | None = None
2023-12-05 22:53:43 +08:00
start_date: datetime
end_date: datetime
2023-12-20 02:01:46 +08:00
voucher_uid: str | None = None
value_percent: float | None = None
value_amount: float | None = None
2023-12-05 22:53:43 +08:00
is_active: bool
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class VoucherInput:
name: str
usage_limit: int
start_date: datetime
end_date: datetime
once_per_customer: bool
once_per_order: bool
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class VoucherCodeInput:
code: str
voucher_uid: str
usage_limit: int
is_active: bool
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class BillTransactionInput:
test_bill_uid: str
kind: str
amount: float
notes: str | None = ""
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class ApplyVoucherInput:
voucher_code: str
2023-12-20 02:01:46 +08:00
test_bill_uid: str
2023-12-05 22:53:43 +08:00
customer_uid: str
@strawberry.type
class BillingMutations:
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_profile_price(
self, info: Info, uid: str, payload: PriceInput
) -> ProfilePriceResponse:
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
2024-07-27 05:29:01 +08:00
profile_price = await ProfilePriceService().get(uid=uid)
2023-12-05 22:53:43 +08:00
incoming: dict = {
"amount": payload.amount,
"is_active": payload.is_active,
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
obj_in = schemas.ProfilePriceUpdate(**incoming)
2024-07-27 05:29:01 +08:00
profile_price = await ProfilePriceService().update(profile_price.uid, obj_in)
2023-12-05 22:53:43 +08:00
return ProfilePriceType(**profile_price.marshal_simple())
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_analysis_price(
self, info: Info, uid: str, payload: PriceInput
) -> AnalysisPriceResponse:
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
2024-07-27 05:29:01 +08:00
analysis_price = await AnalysisPriceService().get(uid=uid)
2023-12-05 22:53:43 +08:00
incoming: dict = {
"amount": payload.amount,
"is_active": payload.is_active,
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
obj_in = schemas.AnalysisPriceUpdate(**incoming)
2024-07-27 05:29:01 +08:00
analysis_price = await AnalysisPriceService().update(analysis_price.uid, obj_in)
2023-12-05 22:53:43 +08:00
return AnalysisPriceType(**analysis_price.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_profile_discount(
self, info, uid: str, payload: PriceDiscountInput
2023-12-05 22:53:43 +08:00
) -> ProfileDiscountResponse:
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
if not uid:
return OperationError(error="No uid provided to identify update obj")
2024-07-27 05:29:01 +08:00
profile_discount = await ProfileDiscountService().get(uid=uid)
2023-12-05 22:53:43 +08:00
if not profile_discount:
return OperationError(
error=f"ProfileDiscount with uid {uid} not found. Cannot update obj ..."
)
obj_data = profile_discount.to_dict()
2023-12-20 02:01:46 +08:00
payload_data = payload.__dict__
if payload_data["discount_type"] == DiscountType.SALE:
del payload_data["voucher_uid"]
2023-12-05 22:53:43 +08:00
for field in obj_data:
2023-12-20 02:01:46 +08:00
if field in payload_data:
2023-12-05 22:53:43 +08:00
try:
2023-12-20 02:01:46 +08:00
setattr(profile_discount, field, payload_data[field])
2023-12-05 22:53:43 +08:00
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
update_in: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
2023-12-20 02:01:46 +08:00
obj_in = schemas.ProfileDiscountUpdate(
**{**profile_discount.to_dict(), **update_in}
)
profile_discount = await ProfileDiscountService().update(
2024-07-28 03:52:31 +08:00
profile_discount.uid, obj_in
)
return ProfileDiscountType(
**profile_discount.marshal_simple(),
)
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_analysis_discount(
self, info, uid: str, payload: PriceDiscountInput
2023-12-05 22:53:43 +08:00
) -> AnalysisDiscountResponse:
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
if not uid:
return OperationError(error="No uid provided to identify update obj")
2024-07-27 05:29:01 +08:00
analysis_discount = await AnalysisDiscountService().get(uid=uid)
2023-12-05 22:53:43 +08:00
if not analysis_discount:
return OperationError(
error=f"AnalysisDiscount with uid {uid} not found. Cannot update obj ..."
)
obj_data = analysis_discount.to_dict()
for field in obj_data:
if field in payload.__dict__:
try:
setattr(analysis_discount, field, payload.__dict__[field])
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
update_in: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
obj_in = schemas.AnalysisDiscountUpdate(
**{**analysis_discount.to_dict(), **update_in}
)
analysis_discount = await AnalysisDiscountService().update(
2024-07-28 03:52:31 +08:00
analysis_discount.uid, obj_in
)
2023-12-05 22:53:43 +08:00
return AnalysisDiscountType(**analysis_discount.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def create_voucher(
self, info: Info, payload: VoucherInput
2023-12-05 22:53:43 +08:00
) -> VoucherResponse:
2024-07-27 03:00:08 +08:00
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
2024-07-27 05:29:01 +08:00
exists = await VoucherService().get(name=payload.name)
2023-12-05 22:53:43 +08:00
if exists:
return OperationError(error=f"Voucher {payload.name} already exists")
2023-12-05 22:53:43 +08:00
incoming: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
for k, v in payload.__dict__.items():
incoming[k] = v
obj_in = schemas.VoucherCreate(**incoming)
2024-07-27 05:29:01 +08:00
voucher = await VoucherService().create(obj_in)
2023-12-05 22:53:43 +08:00
return VoucherType(**voucher.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_voucher(
self, info, uid: str, payload: VoucherInput
2023-12-05 22:53:43 +08:00
) -> VoucherResponse:
2024-07-27 03:00:08 +08:00
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
if not uid:
return OperationError(error="No uid provided to identify update obj")
2024-07-27 05:29:01 +08:00
voucher = await VoucherService().get(uid=uid)
2023-12-05 22:53:43 +08:00
if not voucher:
return OperationError(
error=f"Voucher with uid {uid} not found. Cannot update obj ..."
)
obj_data = voucher.to_dict()
for field in obj_data:
if field in payload.__dict__:
try:
setattr(voucher, field, payload.__dict__[field])
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
obj_in = schemas.VoucherUpdate(**voucher.to_dict())
2024-07-27 05:29:01 +08:00
voucher = await VoucherService().update(voucher.uid, obj_in)
2023-12-05 22:53:43 +08:00
return VoucherType(**voucher.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def create_voucher_code(
self, info: Info, payload: VoucherCodeInput
2023-12-05 22:53:43 +08:00
) -> VoucherCodeResponse:
2024-07-27 03:00:08 +08:00
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
2024-07-27 05:29:01 +08:00
exists = await VoucherCodeService().get(code=payload.code)
2023-12-05 22:53:43 +08:00
if exists:
return OperationError(error=f"Voucher Code {payload.code} already exists")
2023-12-05 22:53:43 +08:00
incoming: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
for k, v in payload.__dict__.items():
incoming[k] = v
obj_in = schemas.VoucherCodeCreate(**incoming)
2024-07-27 05:29:01 +08:00
voucher_code = await VoucherCodeService().create(obj_in)
2023-12-05 22:53:43 +08:00
return VoucherCodeType(**voucher_code.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_voucher_code(
self, info, uid: str, payload: VoucherCodeInput
2023-12-20 02:01:46 +08:00
) -> VoucherCodeResponse:
2023-12-05 22:53:43 +08:00
2024-07-27 03:00:08 +08:00
felicity_user = await auth_from_info(info)
2023-12-05 22:53:43 +08:00
if not uid:
return OperationError(error="No uid provided to identify update obj")
2024-07-27 05:29:01 +08:00
voucher_code = await VoucherCodeService().get(uid=uid)
2023-12-05 22:53:43 +08:00
if not voucher_code:
return OperationError(
error=f"Voucher with uid {uid} not found. Cannot update obj ..."
)
obj_data = voucher_code.to_dict()
for field in obj_data:
if field in payload.__dict__:
try:
setattr(voucher_code, field, payload.__dict__[field])
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
obj_in = schemas.VoucherCodeUpdate(**voucher_code.to_dict())
2024-07-27 05:29:01 +08:00
voucher_code = await VoucherCodeService().update(voucher_code.uid, obj_in)
2023-12-20 02:01:46 +08:00
return VoucherCodeType(**voucher_code.marshal_simple())
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def create_test_bill_transaction(
self, info, payload: BillTransactionInput
2023-12-05 22:53:43 +08:00
) -> TestBillTransactionResponse:
2024-07-27 03:00:08 +08:00
felicity_user = await auth_from_info(info)
2023-12-20 02:01:46 +08:00
if payload.amount <= 0:
return OperationError(
error="Invalid transaction Amount.",
suggestion="Transaction amount must be greater than 0",
2023-12-20 02:01:46 +08:00
)
2024-07-27 05:29:01 +08:00
test_bill = await TestBillService().get(uid=payload.test_bill_uid)
2023-12-05 22:53:43 +08:00
incoming: dict = {
2023-12-20 02:01:46 +08:00
"patient_uid": test_bill.patient_uid,
"client_uid": test_bill.client_uid,
2023-12-05 22:53:43 +08:00
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
for k, v in payload.__dict__.items():
incoming[k] = v
obj_in = schemas.TestBillTransactionCreate(**incoming)
2024-07-27 05:29:01 +08:00
tbt = await TestBillTransactionService().create(obj_in)
2023-12-05 22:53:43 +08:00
2023-12-20 02:01:46 +08:00
test_bill_update = {
"total_paid": test_bill.total_paid + tbt.amount,
"partial": True,
2023-12-20 02:01:46 +08:00
}
if test_bill_update["total_paid"] >= test_bill.total_charged:
test_bill_update["partial"] = False
test_bill_update["is_active"] = False
2024-07-28 03:52:31 +08:00
await TestBillService().update(
test_bill.uid, TestBillUpdate(**test_bill_update)
)
2023-12-20 02:01:46 +08:00
transaction_update = {"is_success": True}
if payload.kind == TransactionKind.CASH:
transaction_update["action_required"] = False
transaction_update["processed"] = True
else:
incoming["action_required"] = False
incoming["action_message"] = "Confirm funds reception"
2024-07-28 03:52:31 +08:00
tbt = await TestBillTransactionService().update(
tbt.uid, TestBillTransactionUpdate(**transaction_update)
)
2024-06-28 02:16:12 +08:00
await invoice_utils.impress_invoice(test_bill)
2023-12-20 02:01:46 +08:00
return TestBillTransactionType(**tbt.marshal_simple())
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def apply_voucher(
self, info, payload: ApplyVoucherInput
2023-12-05 22:53:43 +08:00
) -> TestBillTransactionResponse:
2024-07-27 03:00:08 +08:00
felicity_user = await auth_from_info(info)
bill = await utils.apply_voucher(
payload.voucher_code, payload.test_bill_uid, payload.customer_uid
)
2023-12-20 02:01:46 +08:00
return TestBillType(**bill.marshal_simple(exclude=["orders"]))