Spaces:
Paused
Paused
Commit ·
f5b378b
1
Parent(s): 1326afb
feat(customers): add endpoint to retrieve projects associated with a specific customer with pagination
Browse files
app/controllers/customers.py
CHANGED
|
@@ -5,9 +5,11 @@ from app.services.customer_service import CustomerService
|
|
| 5 |
from app.services.customer_list_service import CustomerListService
|
| 6 |
from app.services.contact_service import ContactService
|
| 7 |
from app.services.address_service import AddressService
|
|
|
|
| 8 |
from app.schemas.customer import CustomerCreate, CustomerOut, CustomerUpdate
|
| 9 |
from app.schemas.contact import ContactCreate, ContactOut
|
| 10 |
from app.schemas.address import AddressCreate, AddressOut
|
|
|
|
| 11 |
from app.schemas.paginated_response import PaginatedResponse
|
| 12 |
from typing import List, Optional
|
| 13 |
import logging
|
|
@@ -391,4 +393,24 @@ def delete_customer_contact(
|
|
| 391 |
detail="Failed to delete contact"
|
| 392 |
)
|
| 393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
|
|
|
|
| 5 |
from app.services.customer_list_service import CustomerListService
|
| 6 |
from app.services.contact_service import ContactService
|
| 7 |
from app.services.address_service import AddressService
|
| 8 |
+
from app.services.project_service import ProjectService
|
| 9 |
from app.schemas.customer import CustomerCreate, CustomerOut, CustomerUpdate
|
| 10 |
from app.schemas.contact import ContactCreate, ContactOut
|
| 11 |
from app.schemas.address import AddressCreate, AddressOut
|
| 12 |
+
from app.schemas.project_detail import ProjectCustomerOut
|
| 13 |
from app.schemas.paginated_response import PaginatedResponse
|
| 14 |
from typing import List, Optional
|
| 15 |
import logging
|
|
|
|
| 393 |
detail="Failed to delete contact"
|
| 394 |
)
|
| 395 |
|
| 396 |
+
# Project-related endpoints
|
| 397 |
+
@router.get("/{customer_id}/projects", response_model=List[ProjectCustomerOut])
|
| 398 |
+
def get_customer_projects(
|
| 399 |
+
customer_id: str,
|
| 400 |
+
page: Optional[int] = Query(1, description="Page number (1-indexed)", ge=1),
|
| 401 |
+
page_size: Optional[int] = Query(100, description="Number of records per page", ge=1, le=1000),
|
| 402 |
+
db: Session = Depends(get_db)
|
| 403 |
+
):
|
| 404 |
+
"""Get all projects associated with a specific customer
|
| 405 |
+
|
| 406 |
+
Returns a paginated list of all project-bidder relationships for the specified
|
| 407 |
+
customer ID, including complete details (barrier sizes, contacts, notes) for each.
|
| 408 |
+
|
| 409 |
+
- **customer_id**: The customer ID (CustId from Bidders table)
|
| 410 |
+
- **page**: Page number starting from 1
|
| 411 |
+
- **page_size**: Number of records per page (max 1000)
|
| 412 |
+
"""
|
| 413 |
+
service = ProjectService(db)
|
| 414 |
+
return service.get_customer_projects(customer_id, page=page, page_size=page_size)
|
| 415 |
+
|
| 416 |
|
app/db/repositories/bidder_repo.py
CHANGED
|
@@ -156,6 +156,54 @@ class BidderRepository:
|
|
| 156 |
logger.warning(f"Error fetching bidder for project {project_no}, customer {customer_id}: {e}")
|
| 157 |
return None
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
def get_bidder_barrier_sizes_raw(self, bidder_id: int):
|
| 160 |
"""Return raw barrier size rows for a bidder (list of dicts)."""
|
| 161 |
try:
|
|
|
|
| 156 |
logger.warning(f"Error fetching bidder for project {project_no}, customer {customer_id}: {e}")
|
| 157 |
return None
|
| 158 |
|
| 159 |
+
def fetch_bidders_by_customer_id_raw(self, customer_id: str, page: int = 1, page_size: int = 100):
|
| 160 |
+
"""Fetch all bidders (project associations) for a specific customer ID with pagination.
|
| 161 |
+
|
| 162 |
+
Returns a list of dicts representing all projects the customer is associated with.
|
| 163 |
+
"""
|
| 164 |
+
try:
|
| 165 |
+
with self.db.get_bind().connect() as conn:
|
| 166 |
+
bidders_query = text("""
|
| 167 |
+
SELECT
|
| 168 |
+
ProjNo as proj_no,
|
| 169 |
+
CustId as cust_id,
|
| 170 |
+
Quote as quote,
|
| 171 |
+
Contact as contact,
|
| 172 |
+
Phone as phone,
|
| 173 |
+
Notes as notes,
|
| 174 |
+
DateLastContact as date_last_contact,
|
| 175 |
+
DateFollowup as date_followup,
|
| 176 |
+
[Primary] as is_primary,
|
| 177 |
+
CustType as cust_type,
|
| 178 |
+
EmailAddress as email_address,
|
| 179 |
+
Id,
|
| 180 |
+
Fax as fax,
|
| 181 |
+
OrderNr as order_nr,
|
| 182 |
+
CustomerPO as customer_po,
|
| 183 |
+
ShipDate as ship_date,
|
| 184 |
+
DeliverDate as deliver_date,
|
| 185 |
+
ReplacementCost as replacement_cost,
|
| 186 |
+
QuoteDate as quote_date,
|
| 187 |
+
InvoiceDate as invoice_date,
|
| 188 |
+
LessPayment as less_payment,
|
| 189 |
+
Enabled as enabled,
|
| 190 |
+
EmployeeId as employee_id
|
| 191 |
+
FROM Bidders
|
| 192 |
+
WHERE CustId = :customer_id
|
| 193 |
+
ORDER BY ProjNo DESC, Id
|
| 194 |
+
OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY
|
| 195 |
+
""")
|
| 196 |
+
|
| 197 |
+
offset = max(0, (page - 1) * page_size)
|
| 198 |
+
limit = max(1, min(page_size, 1000))
|
| 199 |
+
result = conn.execute(bidders_query, {"customer_id": customer_id, "offset": offset, "limit": limit})
|
| 200 |
+
rows = result.fetchall()
|
| 201 |
+
columns = result.keys()
|
| 202 |
+
return [dict(zip(columns, row)) for row in rows]
|
| 203 |
+
except Exception as e:
|
| 204 |
+
logger.warning(f"Error fetching bidders for customer {customer_id}: {e}")
|
| 205 |
+
return []
|
| 206 |
+
|
| 207 |
def get_bidder_barrier_sizes_raw(self, bidder_id: int):
|
| 208 |
"""Return raw barrier size rows for a bidder (list of dicts)."""
|
| 209 |
try:
|
app/services/project_service.py
CHANGED
|
@@ -239,6 +239,122 @@ class ProjectService:
|
|
| 239 |
|
| 240 |
return customer
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
def list_projects(self, customer_type: int = 0, order_by: str = "project_no",
|
| 243 |
order_direction: str = "asc", page: int = 1, page_size: int = 10) -> PaginatedResponse[ProjectOut]:
|
| 244 |
"""
|
|
|
|
| 239 |
|
| 240 |
return customer
|
| 241 |
|
| 242 |
+
def get_customer_projects(self, customer_id: str, page: int = 1, page_size: int = 100) -> List[ProjectCustomerOut]:
|
| 243 |
+
"""
|
| 244 |
+
Get all projects associated with a specific customer.
|
| 245 |
+
|
| 246 |
+
Args:
|
| 247 |
+
customer_id: The customer ID (CustId)
|
| 248 |
+
page: Page number for pagination
|
| 249 |
+
page_size: Number of records per page
|
| 250 |
+
|
| 251 |
+
Returns:
|
| 252 |
+
List[ProjectCustomerOut]: List of project-bidder relationships with full details
|
| 253 |
+
"""
|
| 254 |
+
from app.db.repositories.bidder_repo import BidderRepository
|
| 255 |
+
|
| 256 |
+
bidder_repo = BidderRepository(self.db)
|
| 257 |
+
bidder_rows = bidder_repo.fetch_bidders_by_customer_id_raw(customer_id, page=page, page_size=page_size)
|
| 258 |
+
|
| 259 |
+
logger.info(f"Repository returned {len(bidder_rows)} bidders for customer {customer_id}")
|
| 260 |
+
|
| 261 |
+
customers = []
|
| 262 |
+
for bidder_data in bidder_rows:
|
| 263 |
+
bidder_id = bidder_data.get('Id')
|
| 264 |
+
|
| 265 |
+
# Fetch related data
|
| 266 |
+
barrier_rows = bidder_repo.get_bidder_barrier_sizes_raw(bidder_id)
|
| 267 |
+
contacts_rows = bidder_repo.get_bidder_contacts_raw(bidder_id)
|
| 268 |
+
notes_rows = bidder_repo.get_bidder_notes_raw(bidder_id)
|
| 269 |
+
|
| 270 |
+
# Map barrier rows to BarrierSizeOut
|
| 271 |
+
barrier_sizes = []
|
| 272 |
+
for br in barrier_rows:
|
| 273 |
+
barrier_sizes.append(BarrierSizeOut(
|
| 274 |
+
id=br.get('Id', 0),
|
| 275 |
+
inventory_id=str(br.get('InventoryId', '')) if br.get('InventoryId') is not None else None,
|
| 276 |
+
bidder_id=bidder_id,
|
| 277 |
+
barrier_size_id=br.get('BarrierSizeId', 0),
|
| 278 |
+
install_advisor_fees=br.get('InstallAdvisorFees'),
|
| 279 |
+
is_standard=bool(br.get('IsStandard', True)),
|
| 280 |
+
width=br.get('Width'),
|
| 281 |
+
length=br.get('Length'),
|
| 282 |
+
cable_units=br.get('CableUnits'),
|
| 283 |
+
height=br.get('Height'),
|
| 284 |
+
price=br.get('Price')
|
| 285 |
+
))
|
| 286 |
+
|
| 287 |
+
# Map contacts
|
| 288 |
+
contacts = []
|
| 289 |
+
for cr in contacts_rows:
|
| 290 |
+
contacts.append(ContactOut(
|
| 291 |
+
id=cr.get('Id'),
|
| 292 |
+
contact_id=cr.get('ContactId'),
|
| 293 |
+
bidder_id=bidder_id,
|
| 294 |
+
enabled=bool(cr.get('Enabled', True)),
|
| 295 |
+
first_name=cr.get('FirstName'),
|
| 296 |
+
last_name=cr.get('LastName'),
|
| 297 |
+
title=cr.get('Title'),
|
| 298 |
+
email=cr.get('EmailAddress'),
|
| 299 |
+
phones=[],
|
| 300 |
+
phone1=cr.get('WorkPhone'),
|
| 301 |
+
phone2=cr.get('MobilePhone')
|
| 302 |
+
))
|
| 303 |
+
|
| 304 |
+
# Map notes
|
| 305 |
+
bidder_notes = []
|
| 306 |
+
for nr in notes_rows:
|
| 307 |
+
bidder_notes.append(BidderNoteOut(
|
| 308 |
+
id=nr.get('Id'),
|
| 309 |
+
bidder_id=bidder_id,
|
| 310 |
+
date=nr.get('Date'),
|
| 311 |
+
employee_id=nr.get('EmployeeID'),
|
| 312 |
+
notes=nr.get('Notes', '')
|
| 313 |
+
))
|
| 314 |
+
|
| 315 |
+
# Create customer object with proper type conversions
|
| 316 |
+
replacement_cost = bidder_data.get('replacement_cost')
|
| 317 |
+
if replacement_cost == '' or replacement_cost is None:
|
| 318 |
+
replacement_cost = None
|
| 319 |
+
|
| 320 |
+
cust_type = bidder_data.get('cust_type')
|
| 321 |
+
if cust_type is not None:
|
| 322 |
+
cust_type = str(cust_type)
|
| 323 |
+
|
| 324 |
+
customer = ProjectCustomerOut(
|
| 325 |
+
proj_no=bidder_data.get('proj_no', 0),
|
| 326 |
+
cust_id=str(bidder_data.get('cust_id', '')),
|
| 327 |
+
quote=bidder_data.get('quote'),
|
| 328 |
+
contact=bidder_data.get('contact'),
|
| 329 |
+
phone=bidder_data.get('phone'),
|
| 330 |
+
notes=bidder_data.get('notes'),
|
| 331 |
+
date_last_contact=bidder_data.get('date_last_contact'),
|
| 332 |
+
date_followup=bidder_data.get('date_followup'),
|
| 333 |
+
primary=bool(bidder_data.get('is_primary', False)),
|
| 334 |
+
cust_type=cust_type,
|
| 335 |
+
email_address=bidder_data.get('email_address'),
|
| 336 |
+
id=bidder_id,
|
| 337 |
+
fax=bidder_data.get('fax'),
|
| 338 |
+
order_nr=bidder_data.get('order_nr'),
|
| 339 |
+
customer_po=bidder_data.get('customer_po'),
|
| 340 |
+
ship_date=bidder_data.get('ship_date'),
|
| 341 |
+
deliver_date=bidder_data.get('deliver_date'),
|
| 342 |
+
replacement_cost=replacement_cost,
|
| 343 |
+
quote_date=bidder_data.get('quote_date'),
|
| 344 |
+
invoice_date=bidder_data.get('invoice_date'),
|
| 345 |
+
less_payment=bidder_data.get('less_payment'),
|
| 346 |
+
barrier_sizes=barrier_sizes,
|
| 347 |
+
contacts=contacts,
|
| 348 |
+
bidder_notes=bidder_notes,
|
| 349 |
+
bid_date=bidder_data.get('date_last_contact'), # Using last contact as bid date
|
| 350 |
+
enabled=bool(bidder_data.get('enabled', True)),
|
| 351 |
+
employee_id=str(bidder_data.get('employee_id')) if bidder_data.get('employee_id') is not None else None
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
customers.append(customer)
|
| 355 |
+
|
| 356 |
+
return customers
|
| 357 |
+
|
| 358 |
def list_projects(self, customer_type: int = 0, order_by: str = "project_no",
|
| 359 |
order_direction: str = "asc", page: int = 1, page_size: int = 10) -> PaginatedResponse[ProjectOut]:
|
| 360 |
"""
|
app/tests/unit/test_project_customers.py
CHANGED
|
@@ -5,6 +5,7 @@ from app.schemas.project_detail import ProjectCustomerOut, BarrierSizeOut, Conta
|
|
| 5 |
|
| 6 |
from unittest.mock import MagicMock
|
| 7 |
import app.controllers.projects as projects_controller
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
def make_sample_bidder_row():
|
|
@@ -187,4 +188,123 @@ def test_get_project_customer_detail_endpoint(monkeypatch):
|
|
| 187 |
assert result.cust_id == 'CUST777'
|
| 188 |
assert result.id == 77
|
| 189 |
assert result.order_nr == 'ORD123'
|
| 190 |
-
assert result.employee_id == 'EMP5'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
from unittest.mock import MagicMock
|
| 7 |
import app.controllers.projects as projects_controller
|
| 8 |
+
import app.controllers.customers as customers_controller
|
| 9 |
|
| 10 |
|
| 11 |
def make_sample_bidder_row():
|
|
|
|
| 188 |
assert result.cust_id == 'CUST777'
|
| 189 |
assert result.id == 77
|
| 190 |
assert result.order_nr == 'ORD123'
|
| 191 |
+
assert result.employee_id == 'EMP5'
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def test_get_customer_projects_service(monkeypatch):
|
| 195 |
+
"""Test ProjectService.get_customer_projects method"""
|
| 196 |
+
mock_db = Mock()
|
| 197 |
+
service = ProjectService(mock_db)
|
| 198 |
+
|
| 199 |
+
# Create sample bidders for multiple projects
|
| 200 |
+
bidder1 = make_sample_bidder_row()
|
| 201 |
+
bidder1['proj_no'] = 100
|
| 202 |
+
bidder1['cust_id'] = 'CUST888'
|
| 203 |
+
bidder1['Id'] = 20
|
| 204 |
+
|
| 205 |
+
bidder2 = make_sample_bidder_row()
|
| 206 |
+
bidder2['proj_no'] = 200
|
| 207 |
+
bidder2['cust_id'] = 'CUST888'
|
| 208 |
+
bidder2['Id'] = 21
|
| 209 |
+
|
| 210 |
+
# Mock BidderRepository methods
|
| 211 |
+
from app.db.repositories.bidder_repo import BidderRepository
|
| 212 |
+
mock_bidder_repo = Mock(spec=BidderRepository)
|
| 213 |
+
mock_bidder_repo.fetch_bidders_by_customer_id_raw = Mock(return_value=[bidder1, bidder2])
|
| 214 |
+
mock_bidder_repo.get_bidder_barrier_sizes_raw = Mock(return_value=[])
|
| 215 |
+
mock_bidder_repo.get_bidder_contacts_raw = Mock(return_value=[])
|
| 216 |
+
mock_bidder_repo.get_bidder_notes_raw = Mock(return_value=[])
|
| 217 |
+
|
| 218 |
+
# Patch BidderRepository instantiation
|
| 219 |
+
def fake_bidder_repo_init(db):
|
| 220 |
+
return mock_bidder_repo
|
| 221 |
+
|
| 222 |
+
monkeypatch.setattr('app.db.repositories.bidder_repo.BidderRepository', fake_bidder_repo_init)
|
| 223 |
+
|
| 224 |
+
projects = service.get_customer_projects('CUST888', page=1, page_size=10)
|
| 225 |
+
|
| 226 |
+
assert isinstance(projects, list)
|
| 227 |
+
assert len(projects) == 2
|
| 228 |
+
assert projects[0].proj_no == 100
|
| 229 |
+
assert projects[0].cust_id == 'CUST888'
|
| 230 |
+
assert projects[1].proj_no == 200
|
| 231 |
+
assert projects[1].cust_id == 'CUST888'
|
| 232 |
+
mock_bidder_repo.fetch_bidders_by_customer_id_raw.assert_called_once_with('CUST888', page=1, page_size=10)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def test_get_customer_projects_endpoint(monkeypatch):
|
| 236 |
+
"""Test controller endpoint for customer projects"""
|
| 237 |
+
sample1 = ProjectCustomerOut(
|
| 238 |
+
proj_no=300,
|
| 239 |
+
cust_id='CUST555',
|
| 240 |
+
quote=None,
|
| 241 |
+
contact='Project A Contact',
|
| 242 |
+
phone='111',
|
| 243 |
+
notes=None,
|
| 244 |
+
date_last_contact=None,
|
| 245 |
+
date_followup=None,
|
| 246 |
+
primary=True,
|
| 247 |
+
cust_type='1',
|
| 248 |
+
email_address='a@example.com',
|
| 249 |
+
id=30,
|
| 250 |
+
fax=None,
|
| 251 |
+
order_nr=None,
|
| 252 |
+
customer_po=None,
|
| 253 |
+
ship_date=None,
|
| 254 |
+
deliver_date=None,
|
| 255 |
+
replacement_cost=None,
|
| 256 |
+
quote_date=None,
|
| 257 |
+
invoice_date=None,
|
| 258 |
+
less_payment=None,
|
| 259 |
+
barrier_sizes=[],
|
| 260 |
+
contacts=[],
|
| 261 |
+
bidder_notes=[],
|
| 262 |
+
bid_date=None,
|
| 263 |
+
enabled=True,
|
| 264 |
+
employee_id=None
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
sample2 = ProjectCustomerOut(
|
| 268 |
+
proj_no=400,
|
| 269 |
+
cust_id='CUST555',
|
| 270 |
+
quote=None,
|
| 271 |
+
contact='Project B Contact',
|
| 272 |
+
phone='222',
|
| 273 |
+
notes=None,
|
| 274 |
+
date_last_contact=None,
|
| 275 |
+
date_followup=None,
|
| 276 |
+
primary=False,
|
| 277 |
+
cust_type='2',
|
| 278 |
+
email_address='b@example.com',
|
| 279 |
+
id=31,
|
| 280 |
+
fax=None,
|
| 281 |
+
order_nr=None,
|
| 282 |
+
customer_po=None,
|
| 283 |
+
ship_date=None,
|
| 284 |
+
deliver_date=None,
|
| 285 |
+
replacement_cost=None,
|
| 286 |
+
quote_date=None,
|
| 287 |
+
invoice_date=None,
|
| 288 |
+
less_payment=None,
|
| 289 |
+
barrier_sizes=[],
|
| 290 |
+
contacts=[],
|
| 291 |
+
bidder_notes=[],
|
| 292 |
+
bid_date=None,
|
| 293 |
+
enabled=True,
|
| 294 |
+
employee_id=None
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
def fake_get_customer_projects(self, customer_id, page=1, page_size=100):
|
| 298 |
+
return [sample1, sample2]
|
| 299 |
+
|
| 300 |
+
monkeypatch.setattr('app.services.project_service.ProjectService.get_customer_projects', fake_get_customer_projects)
|
| 301 |
+
|
| 302 |
+
fake_db = MagicMock()
|
| 303 |
+
result = customers_controller.get_customer_projects('CUST555', page=1, page_size=10, db=fake_db)
|
| 304 |
+
|
| 305 |
+
assert isinstance(result, list)
|
| 306 |
+
assert len(result) == 2
|
| 307 |
+
assert result[0].proj_no == 300
|
| 308 |
+
assert result[0].cust_id == 'CUST555'
|
| 309 |
+
assert result[1].proj_no == 400
|
| 310 |
+
assert result[1].cust_id == 'CUST555'
|