Replace the raw EXISTS subquery (items within an item-group subtree) with
frappe.qb (item_group.isin(subquery)). Same result on MariaDB; valid under
Postgres' stricter SQL.
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>
Convert raw `frappe.db.sql` in the Setup, Utilities, Templates and Regional
areas 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, reserved words).
Conversions of note (behaviour kept identical to the MariaDB original):
- email_digest: ToDo ordering replicated with a CASE that mirrors MySQL
`field(priority,'High','Medium','Low')` (unknown/NULL -> 0, sorts first),
NULL-date-first and a `name` tie-break for a deterministic LIMIT.
- company.get_all_transactions_annual_history: the cross-DocType UNION + GROUP BY
is replaced by one grouped query per DocType merged with a Counter, so two
different DocTypes sharing a transaction_date still collapse into one bucket.
- templates/utils.send_message: contact lookup wraps both sides in LOWER() to
keep MariaDB's case-insensitive email match on case-sensitive Postgres.
- regional/irs_1099 & uae_vat_201: address ranking and emirate aggregation
rebuilt with CASE/aggregate selects that satisfy Postgres GROUP BY, with a
deterministic tie-break on the LIMIT-1 address lookups.
- utilities/product.get_item_codes_by_attributes: numeric attribute values are
cast with cstr() so Postgres doesn't reject `varchar = numeric`.
Tests (run on both MariaDB and Postgres, --lightmode):
- New: company merge test, authorization_rule duplicate-check, youtube report,
templates/utils, and utilities/templates page reports (partners, rfq,
material_request_info, product, utilities __init__).
- Existing suites kept green: company, email_digest, transaction_deletion_record,
irs_1099, uae_vat_201.
Deferred (tracked separately):
- setup/doctype/authorization_control.py still has raw `.format()` SELECTs;
left for its own PR.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>