get_default_contact / get_default_address: raw correlated-subquery SELECTs over
Dynamic Link -> frappe.qb with a LEFT join (preserving the original
correlated-subquery semantics: a Dynamic Link whose parent Contact/Address is
missing still returns, with a NULL flag). Same result on MariaDB; valid under
Postgres.
Tests: pin the converted query output (real linked Contact/Address) and lock
the LEFT-join choice with an orphaned-Dynamic-Link case (fails under an inner
join).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
update_billed_amount_based_on_so: raw "select sum(amount) ... where
dn_detail=%s and docstatus=1" -> frappe.get_all(fields=[{SUM: amount}]); the
bare aggregate needs no GROUP BY and the NULL-sum still resolves to 0. Same
result on MariaDB; valid under Postgres. Covered by the existing billing tests
in test_delivery_note.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cancel_packing_slips: raw "SELECT name FROM `tabPacking Slip` WHERE
delivery_note=%s AND docstatus=1" -> frappe.get_all(pluck="name") with
pluck-aware iteration. Same result on MariaDB; valid under Postgres.
Covered by test_cancel_packing_slips_cancels_submitted_slips in
test_delivery_note.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
set_actual_qty Bin lookup -> frappe.db.get_value; validate_proj_cust raw
"customer=%s OR ifnull(customer,'')=''" -> get_all or_filters with
[customer, is, not set] (correct PG empty-string/NULL handling); the two
check_next_docstatus implicit comma-joins -> get_all on the child table
(Sales Invoice Item / Installation Note Item, docstatus=1). Same result on
MariaDB; valid under Postgres.
Tests: validate_proj_cust mismatch + no-customer (the or_filters branch), and
check_next_docstatus blocking cancel when a submitted Sales Invoice draws from
the DN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
make_timesheet(simulate=True) logs at now_datetime(); when the suite runs late
in the day under the site timezone the 2h log crosses midnight, so its to_time
falls outside the report's `to_time <= end-of-day` bound and the submitted
timesheet is (correctly) excluded — making test_submitted_timesheet_in_summary
fail in an evening window (observed at 22:51 IST in CI). Pin the log to a fixed
mid-day window on today so the assertion is time-of-day independent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The function was 199 lines, dominated by a 102-line update_details closure.
Extract the two party-mapping branches into module helpers
_apply_purchase_party_details / _apply_sales_party_details and the address
lookup into _get_linked_address; update_details is now a 6-line dispatcher.
make_inter_company_transaction drops to ~104 lines. No behaviour change
(inter-company SI->PI and PO->SO suites green).
Rename three private helpers to follow the verb-prefixed convention used
across the services:
- _amount_in_account_currency -> _get_amount_in_account_currency
- _return_aware_against_voucher -> _resolve_against_voucher
- _sdbnb_booking_for_item -> _get_sdbnb_booking_for_item
Pure rename of private methods, no behaviour change.
set_status was a single status-resolution cascade (cyclomatic complexity
C/19). Extract the submitted-invoice resolution into _submitted_status (with
the invoice-discounting suffix) and _payment_status (guard clauses), leaving
the cancelled/draft quirks inline. set_status drops C/19 -> B/7; no C-rank
function remains in the module.
Add a characterization test for set_indicator, which was 93% untested -
the portal indicator colour/title for credit-note / unpaid / overdue /
return / paid states. Behaviour is unchanged (status and invoice-discounting
suites green).
The link-decision was a single four-way boolean OR (cyclomatic complexity
C/16) where every branch repeated 'args.timesheet_detail == data.name'.
Factor that match out as a loop guard and extract the remaining decision into
_should_set_sales_invoice as ordered guard clauses. Behaviour is unchanged
(project, link-on-submit, unlink-on-cancel and return paths preserved);
characterization tests and the full timesheet suite are green.
Pin TimesheetBillingService behaviour before refactor: billing a timesheet
into a Sales Invoice links the timesheet detail and marks it Billed on submit,
and clears the link / reverts status on cancel; an unsubmitted timesheet
cannot be invoiced.
Convert raw `frappe.db.sql` across the Projects module to `frappe.qb` / the ORM
so the same code runs on MariaDB and Postgres. Behaviour is preserved on
MariaDB; the conversions also make these paths valid under Postgres' stricter
SQL (GROUP BY, case-sensitivity, empty-string handling).
Conversions of note (behaviour kept identical to the MariaDB original):
- project.get_users_for_project: search selects the stored full_name instead of
concat_ws(first, middle, last) (concat_ws diverges on Postgres, where empty
Data fields are NULL) and wraps Locate in LOWER() to keep MariaDB's
case-insensitive result ordering.
- project costing: percent-complete and sales/billed-amount aggregates rebuilt
as Sum() query-builder selects.
- task.reschedule_dependent_tasks: the correlated subquery is split into a
`Task Depends On` parent-pluck + a Task lookup (same rows, no nested SQL).
- timesheet.get_events: user-permission match conditions move to the query-builder
form via get_event_conditions_qb; calendar columns rebuilt with Concat/Round.
- report/project_wise_stock_tracking & report/daily_timesheet_summary: GROUP BY
cost aggregates and the timesheet date window (timestamp(to_date,'24:00:00') ->
end-of-day via get_combine_datetime) rebuilt to satisfy Postgres.
- search helpers (query_task, get_project, get_timesheet) use frappe.qb.get_query
with ignore_permissions=False in place of build_match_conditions/get_match_cond.
Tests (run on both MariaDB and Postgres, --lightmode):
- Existing project/task/timesheet/activity_cost suites kept green (27 tests).
- New project_wise_stock_tracking test drives all three cost aggregates with
positive data (purchased / issued / delivered GROUP BY) plus get_project_details.
- New daily_timesheet_summary test covers the date-window join.
Not included: project_update.py is deferred. Its daily_reminder()/email_sending()
select `progress`/`progress_details`, columns that do not exist on the Project
Update doctype, so the function errors when invoked regardless of backend - a
pre-existing bug that needs an email-rework, not just a query port.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(coa_importer): allow importing COA only for `Accounts Manager`
Co-authored-by: Pratheep S <pratheeps2024@gmail.com>
* fix(coa_importer): check permissions in `unset_existing_data`
---------
Co-authored-by: Pratheep S <pratheeps2024@gmail.com>