Spaces:
Running
Running
Commit ·
165d873
1
Parent(s): e3c6991
(invoice): implement global meta schema for invoice module with audit information
Browse files
app/trade_invoices/controllers/router.py
CHANGED
|
@@ -50,11 +50,13 @@ async def create_invoice(
|
|
| 50 |
"""
|
| 51 |
try:
|
| 52 |
created_by = current_user.user_id
|
|
|
|
| 53 |
|
| 54 |
invoice, errors = await TradeInvoiceService.create_draft_invoice(
|
| 55 |
db=db,
|
| 56 |
invoice_data=invoice_data,
|
| 57 |
-
created_by=created_by
|
|
|
|
| 58 |
)
|
| 59 |
|
| 60 |
if errors:
|
|
@@ -165,12 +167,14 @@ async def edit_invoice(
|
|
| 165 |
"""
|
| 166 |
try:
|
| 167 |
updated_by = current_user.user_id
|
|
|
|
| 168 |
|
| 169 |
invoice, errors = await TradeInvoiceService.edit_draft_invoice(
|
| 170 |
db=db,
|
| 171 |
invoice_id=invoice_id,
|
| 172 |
invoice_data=invoice_data,
|
| 173 |
-
updated_by=updated_by
|
|
|
|
| 174 |
)
|
| 175 |
|
| 176 |
if errors:
|
|
@@ -226,12 +230,14 @@ async def update_invoice_status(
|
|
| 226 |
"""
|
| 227 |
try:
|
| 228 |
performed_by = current_user.user_id
|
|
|
|
| 229 |
|
| 230 |
invoice, errors = await TradeInvoiceService.update_invoice_status(
|
| 231 |
db=db,
|
| 232 |
invoice_id=invoice_id,
|
| 233 |
action_request=action_request,
|
| 234 |
-
performed_by=performed_by
|
|
|
|
| 235 |
)
|
| 236 |
|
| 237 |
if errors:
|
|
|
|
| 50 |
"""
|
| 51 |
try:
|
| 52 |
created_by = current_user.user_id
|
| 53 |
+
created_by_username = current_user.username
|
| 54 |
|
| 55 |
invoice, errors = await TradeInvoiceService.create_draft_invoice(
|
| 56 |
db=db,
|
| 57 |
invoice_data=invoice_data,
|
| 58 |
+
created_by=created_by,
|
| 59 |
+
created_by_username=created_by_username
|
| 60 |
)
|
| 61 |
|
| 62 |
if errors:
|
|
|
|
| 167 |
"""
|
| 168 |
try:
|
| 169 |
updated_by = current_user.user_id
|
| 170 |
+
updated_by_username = current_user.username
|
| 171 |
|
| 172 |
invoice, errors = await TradeInvoiceService.edit_draft_invoice(
|
| 173 |
db=db,
|
| 174 |
invoice_id=invoice_id,
|
| 175 |
invoice_data=invoice_data,
|
| 176 |
+
updated_by=updated_by,
|
| 177 |
+
updated_by_username=updated_by_username
|
| 178 |
)
|
| 179 |
|
| 180 |
if errors:
|
|
|
|
| 230 |
"""
|
| 231 |
try:
|
| 232 |
performed_by = current_user.user_id
|
| 233 |
+
performed_by_username = current_user.username
|
| 234 |
|
| 235 |
invoice, errors = await TradeInvoiceService.update_invoice_status(
|
| 236 |
db=db,
|
| 237 |
invoice_id=invoice_id,
|
| 238 |
action_request=action_request,
|
| 239 |
+
performed_by=performed_by,
|
| 240 |
+
performed_by_username=performed_by_username
|
| 241 |
)
|
| 242 |
|
| 243 |
if errors:
|
app/trade_invoices/models/model.py
CHANGED
|
@@ -99,8 +99,11 @@ class ScmInvoice(Base):
|
|
| 99 |
|
| 100 |
# Audit fields
|
| 101 |
created_by = Column(String(64), nullable=True)
|
|
|
|
| 102 |
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
| 103 |
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
|
| 104 |
|
| 105 |
# Relationships
|
| 106 |
items = relationship("ScmInvoiceItem", back_populates="invoice", cascade="all, delete-orphan")
|
|
|
|
| 99 |
|
| 100 |
# Audit fields
|
| 101 |
created_by = Column(String(64), nullable=True)
|
| 102 |
+
created_by_username = Column(String(128), nullable=True)
|
| 103 |
created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
| 104 |
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
| 105 |
+
updated_by = Column(String(64), nullable=True)
|
| 106 |
+
updated_by_username = Column(String(128), nullable=True)
|
| 107 |
|
| 108 |
# Relationships
|
| 109 |
items = relationship("ScmInvoiceItem", back_populates="invoice", cascade="all, delete-orphan")
|
app/trade_invoices/services/service.py
CHANGED
|
@@ -13,6 +13,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
| 13 |
from sqlalchemy import select, and_, func, text
|
| 14 |
from sqlalchemy.orm import selectinload
|
| 15 |
|
|
|
|
| 16 |
from app.trade_invoices.models.model import ScmInvoice, ScmInvoiceItem, ScmInvoiceStatusLog
|
| 17 |
from app.trade_invoices.schemas.schema import (
|
| 18 |
GSTModel, InvoiceCreate, InvoiceActionRequest, InvoiceValidationError, POItemModel, POShipmentSummary, POSummaryModel, PricingModel, PurchaseOrderResponseModel, QuantityModel
|
|
@@ -39,7 +40,8 @@ class TradeInvoiceService:
|
|
| 39 |
async def create_draft_invoice(
|
| 40 |
db: AsyncSession,
|
| 41 |
invoice_data: InvoiceCreate,
|
| 42 |
-
created_by: str
|
|
|
|
| 43 |
) -> Tuple[ScmInvoice, List[InvoiceValidationError]]:
|
| 44 |
"""
|
| 45 |
Create draft invoice (PO-driven, no GRN)
|
|
@@ -130,7 +132,9 @@ class TradeInvoiceService:
|
|
| 130 |
round_off_amt=invoice_data.additional_charges.round_off or 0,
|
| 131 |
|
| 132 |
remarks=invoice_data.remarks,
|
| 133 |
-
created_by=created_by
|
|
|
|
|
|
|
| 134 |
)
|
| 135 |
|
| 136 |
db.add(invoice)
|
|
@@ -306,7 +310,8 @@ class TradeInvoiceService:
|
|
| 306 |
db: AsyncSession,
|
| 307 |
invoice_id: UUID,
|
| 308 |
action_request: InvoiceActionRequest,
|
| 309 |
-
performed_by: str
|
|
|
|
| 310 |
) -> Tuple[ScmInvoice, List[InvoiceValidationError]]:
|
| 311 |
"""
|
| 312 |
Update invoice status following state machine rules
|
|
@@ -350,6 +355,8 @@ class TradeInvoiceService:
|
|
| 350 |
# Update invoice
|
| 351 |
invoice.status = new_status
|
| 352 |
invoice.updated_at = dt.datetime.utcnow()
|
|
|
|
|
|
|
| 353 |
|
| 354 |
# Create status log
|
| 355 |
status_log = ScmInvoiceStatusLog(
|
|
@@ -460,7 +467,7 @@ class TradeInvoiceService:
|
|
| 460 |
dict(row) for row in logs_result.mappings().all()
|
| 461 |
]
|
| 462 |
|
| 463 |
-
return response
|
| 464 |
|
| 465 |
@staticmethod
|
| 466 |
async def list_invoices(
|
|
@@ -523,7 +530,10 @@ class TradeInvoiceService:
|
|
| 523 |
|
| 524 |
query = text(query_str)
|
| 525 |
result = await db.execute(query, params)
|
| 526 |
-
return [
|
|
|
|
|
|
|
|
|
|
| 527 |
|
| 528 |
else:
|
| 529 |
|
|
@@ -545,7 +555,12 @@ class TradeInvoiceService:
|
|
| 545 |
i.total_tax_amt,
|
| 546 |
i.grand_total_amt,
|
| 547 |
i.status,
|
| 548 |
-
i.created_at
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
FROM trans.scm_invoice i
|
| 550 |
JOIN trans.scm_po po
|
| 551 |
ON po.po_id = i.po_id
|
|
@@ -585,7 +600,10 @@ class TradeInvoiceService:
|
|
| 585 |
result = await db.execute(text(sql), params)
|
| 586 |
rows = result.mappings().all()
|
| 587 |
|
| 588 |
-
return [
|
|
|
|
|
|
|
|
|
|
| 589 |
|
| 590 |
@staticmethod
|
| 591 |
async def get_invoice_summary(
|
|
@@ -685,7 +703,7 @@ class TradeInvoiceService:
|
|
| 685 |
)
|
| 686 |
|
| 687 |
@staticmethod
|
| 688 |
-
async def edit_draft_invoice(db: AsyncSession, invoice_id: str, invoice_data: dict, updated_by: str):
|
| 689 |
EDITABLE_FIELDS = {
|
| 690 |
"po_id": lambda v: UUID(v),
|
| 691 |
"grn_id": lambda v: UUID(v),
|
|
@@ -699,6 +717,8 @@ class TradeInvoiceService:
|
|
| 699 |
"packing_amt": lambda v: v,
|
| 700 |
"other_charges_amt": lambda v: v,
|
| 701 |
"round_off_amt": lambda v: v,
|
|
|
|
|
|
|
| 702 |
}
|
| 703 |
result = await db.execute(
|
| 704 |
select(ScmInvoice).where(ScmInvoice.invoice_id == invoice_id)
|
|
@@ -727,6 +747,8 @@ class TradeInvoiceService:
|
|
| 727 |
setattr(invoice, field, transformer(invoice_data[field]))
|
| 728 |
|
| 729 |
invoice.updated_at = dt.datetime.utcnow()
|
|
|
|
|
|
|
| 730 |
|
| 731 |
await db.commit()
|
| 732 |
await db.refresh(invoice)
|
|
|
|
| 13 |
from sqlalchemy import select, and_, func, text
|
| 14 |
from sqlalchemy.orm import selectinload
|
| 15 |
|
| 16 |
+
from app.core.utils import format_meta_field
|
| 17 |
from app.trade_invoices.models.model import ScmInvoice, ScmInvoiceItem, ScmInvoiceStatusLog
|
| 18 |
from app.trade_invoices.schemas.schema import (
|
| 19 |
GSTModel, InvoiceCreate, InvoiceActionRequest, InvoiceValidationError, POItemModel, POShipmentSummary, POSummaryModel, PricingModel, PurchaseOrderResponseModel, QuantityModel
|
|
|
|
| 40 |
async def create_draft_invoice(
|
| 41 |
db: AsyncSession,
|
| 42 |
invoice_data: InvoiceCreate,
|
| 43 |
+
created_by: str,
|
| 44 |
+
created_by_username:str
|
| 45 |
) -> Tuple[ScmInvoice, List[InvoiceValidationError]]:
|
| 46 |
"""
|
| 47 |
Create draft invoice (PO-driven, no GRN)
|
|
|
|
| 132 |
round_off_amt=invoice_data.additional_charges.round_off or 0,
|
| 133 |
|
| 134 |
remarks=invoice_data.remarks,
|
| 135 |
+
created_by=created_by,
|
| 136 |
+
created_by_username=created_by_username,
|
| 137 |
+
created_at=dt.datetime.utcnow()
|
| 138 |
)
|
| 139 |
|
| 140 |
db.add(invoice)
|
|
|
|
| 310 |
db: AsyncSession,
|
| 311 |
invoice_id: UUID,
|
| 312 |
action_request: InvoiceActionRequest,
|
| 313 |
+
performed_by: str,
|
| 314 |
+
performed_by_username:str
|
| 315 |
) -> Tuple[ScmInvoice, List[InvoiceValidationError]]:
|
| 316 |
"""
|
| 317 |
Update invoice status following state machine rules
|
|
|
|
| 355 |
# Update invoice
|
| 356 |
invoice.status = new_status
|
| 357 |
invoice.updated_at = dt.datetime.utcnow()
|
| 358 |
+
invoice.updated_by = performed_by
|
| 359 |
+
invoice.updated_by_username = performed_by_username
|
| 360 |
|
| 361 |
# Create status log
|
| 362 |
status_log = ScmInvoiceStatusLog(
|
|
|
|
| 467 |
dict(row) for row in logs_result.mappings().all()
|
| 468 |
]
|
| 469 |
|
| 470 |
+
return format_meta_field(response)
|
| 471 |
|
| 472 |
@staticmethod
|
| 473 |
async def list_invoices(
|
|
|
|
| 530 |
|
| 531 |
query = text(query_str)
|
| 532 |
result = await db.execute(query, params)
|
| 533 |
+
return [
|
| 534 |
+
format_meta_field(dict(row._mapping))
|
| 535 |
+
for row in result.fetchall()
|
| 536 |
+
]
|
| 537 |
|
| 538 |
else:
|
| 539 |
|
|
|
|
| 555 |
i.total_tax_amt,
|
| 556 |
i.grand_total_amt,
|
| 557 |
i.status,
|
| 558 |
+
i.created_at,
|
| 559 |
+
i.updated_at,
|
| 560 |
+
i.created_by,
|
| 561 |
+
i.created_by_username,
|
| 562 |
+
i.updated_by,
|
| 563 |
+
i.updated_by_username
|
| 564 |
FROM trans.scm_invoice i
|
| 565 |
JOIN trans.scm_po po
|
| 566 |
ON po.po_id = i.po_id
|
|
|
|
| 600 |
result = await db.execute(text(sql), params)
|
| 601 |
rows = result.mappings().all()
|
| 602 |
|
| 603 |
+
return [
|
| 604 |
+
format_meta_field(dict(row))
|
| 605 |
+
for row in rows
|
| 606 |
+
]
|
| 607 |
|
| 608 |
@staticmethod
|
| 609 |
async def get_invoice_summary(
|
|
|
|
| 703 |
)
|
| 704 |
|
| 705 |
@staticmethod
|
| 706 |
+
async def edit_draft_invoice(db: AsyncSession, invoice_id: str, invoice_data: dict, updated_by: str, updated_by_username:str):
|
| 707 |
EDITABLE_FIELDS = {
|
| 708 |
"po_id": lambda v: UUID(v),
|
| 709 |
"grn_id": lambda v: UUID(v),
|
|
|
|
| 717 |
"packing_amt": lambda v: v,
|
| 718 |
"other_charges_amt": lambda v: v,
|
| 719 |
"round_off_amt": lambda v: v,
|
| 720 |
+
"updated_by": lambda v: v,
|
| 721 |
+
"updated_by_username": lambda v: v
|
| 722 |
}
|
| 723 |
result = await db.execute(
|
| 724 |
select(ScmInvoice).where(ScmInvoice.invoice_id == invoice_id)
|
|
|
|
| 747 |
setattr(invoice, field, transformer(invoice_data[field]))
|
| 748 |
|
| 749 |
invoice.updated_at = dt.datetime.utcnow()
|
| 750 |
+
invoice.updated_by = updated_by
|
| 751 |
+
invoice.updated_by_username = updated_by_username
|
| 752 |
|
| 753 |
await db.commit()
|
| 754 |
await db.refresh(invoice)
|
app/trade_invoices/utils.py
CHANGED
|
@@ -164,7 +164,7 @@ ALLOWED_INVOICE_PROJECTION_FIELDS = [
|
|
| 164 |
"currency", "invoice_date", "payment_terms", "due_date",
|
| 165 |
"subtotal_amt", "discount_amt", "taxable_amt", "cgst_amt", "sgst_amt", "igst_amt",
|
| 166 |
"total_tax_amt", "grand_total_amt", "status", "reverse_charge", "remarks",
|
| 167 |
-
"created_by", "created_at", "updated_at"
|
| 168 |
]
|
| 169 |
|
| 170 |
|
|
|
|
| 164 |
"currency", "invoice_date", "payment_terms", "due_date",
|
| 165 |
"subtotal_amt", "discount_amt", "taxable_amt", "cgst_amt", "sgst_amt", "igst_amt",
|
| 166 |
"total_tax_amt", "grand_total_amt", "status", "reverse_charge", "remarks",
|
| 167 |
+
"created_by", "created_at", "updated_at", "created_by_username", "updated_by_username"
|
| 168 |
]
|
| 169 |
|
| 170 |
|