@waron97/prbot 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,6 +75,8 @@ FirstArg = TypeVar("FirstArg")
75
75
 
76
76
  def first(recordset: FirstArg) -> FirstArg: ...
77
77
  def format_exc() -> str: ...
78
+ def b64encode(s: bytes) -> bytes: ...
79
+ def b64decode(s: Union[str, bytes]) -> bytes: ...
78
80
 
79
81
  # --- Exceptions ---
80
82
 
@@ -134,9 +134,9 @@ from odoo_records import _UomCategory, _UomUom, _UpdateCadastreData, _UtmCampaig
134
134
  from odoo_records import _UtmSource, _UtmStage, _UtmTag, _ValidateAccountMove, _VulnerabilityDashboard, _VulnerabilityDashboardLine
135
135
  from odoo_records import _VulnerabilityHistory, _VulnerabilityNotifyAdmissibility, _VulnerabilityNotifyAdmissibilityLine, _VulnerabilityNotifyOutgoing, _WalletImportWizard, _WalletManagementLine
136
136
  from odoo_records import _WatchlistAddWizard, _WatchlistDownloadWizard, _WatchlistRemoveWizard, _WebEditorAssets, _WebEditorConverterTest, _WebEditorConverterTestSub
137
- from odoo_records import _WebTourTour, _WelcomeAttachmentTable, _WelcomeLetterAttachmentMixin, _WizardIrModelMenuCreate, _WizardUploadVulnerability, _X
138
- from odoo_records import _XCcq, _XMktCom, _XPod, _XPrecheck, _XTopny, _XmlgeneratorDataAggregator
139
- from odoo_records import _XmlgeneratorDataField, _XmlgeneratorDataModel
137
+ from odoo_records import _WebTourTour, _WelcomeAttachmentTable, _WizardIrModelMenuCreate, _WizardUploadVulnerability, _X, _XCcq
138
+ from odoo_records import _XMktCom, _XPod, _XPrecheck, _XTopny, _XmlgeneratorDataAggregator, _XmlgeneratorDataField
139
+ from odoo_records import _XmlgeneratorDataModel
140
140
 
141
141
 
142
142
  class OdooEnvironment:
@@ -1914,10 +1914,6 @@ class OdooEnvironment:
1914
1914
  @overload
1915
1915
  def __getitem__(self, model_name: Literal["welcome.attachment.table"]) -> _WelcomeAttachmentTable: ...
1916
1916
  @overload
1917
- def __getitem__(
1918
- self, model_name: Literal["welcome.letter.attachment.mixin"]
1919
- ) -> _WelcomeLetterAttachmentMixin: ...
1920
- @overload
1921
1917
  def __getitem__(self, model_name: Literal["wizard.ir.model.menu.create"]) -> _WizardIrModelMenuCreate: ...
1922
1918
  @overload
1923
1919
  def __getitem__(self, model_name: Literal["wizard.upload.vulnerability"]) -> _WizardUploadVulnerability: ...
@@ -7253,6 +7253,7 @@ class _CorrectionInvoice(Recordset):
7253
7253
  def _compute_code_supply_point(self): ...
7254
7254
  def _compute_currency(self): ...
7255
7255
  def _get_symphonie_url(self): ...
7256
+ def download_invoice_data(self): ...
7256
7257
  def download_invoice_pdf(self): ...
7257
7258
 
7258
7259
  # --- create.case ---
@@ -9613,7 +9614,7 @@ class _HelpdeskTicket(Recordset):
9613
9614
  disconnect_date: Optional[_dt.date]
9614
9615
  distributor_charge: float
9615
9616
  distributor_code: str
9616
- distributor_id: str
9617
+ distributor_id: "_ResPartner"
9617
9618
  distributor_name: str
9618
9619
  distributor_notes: str
9619
9620
  distributor_outcome_reception_date: Optional[_dt.date]
@@ -9790,6 +9791,7 @@ class _HelpdeskTicket(Recordset):
9790
9791
  meter_reading_message: str
9791
9792
  meter_reading_result: str
9792
9793
  meter_readings: str
9794
+ meter_relocation_note: str
9793
9795
  migrated: bool
9794
9796
  mkt_comm_atti_autorizzativi: str
9795
9797
  mkt_comm_causal_code: str
@@ -10262,6 +10264,7 @@ class _HelpdeskTicket(Recordset):
10262
10264
  def ir_cron_registry_change_customer_privacy(self, triplet_code, days_delta, privacy_flag): ...
10263
10265
  def ir_cron_vulnerable_clients_missing_documentation(self): ...
10264
10266
  def kv_store(self) -> "_SymplePbProcessData": ...
10267
+ def last_staging(self, process_name) -> "_SymplePbProcessData": ...
10265
10268
  def launch_cancel_case_wizard(self): ...
10266
10269
  def launch_connect_case_wizard(self): ...
10267
10270
  def launch_connect_parent_wizard(self): ...
@@ -12161,9 +12164,9 @@ class _IrConfigParameter(Recordset):
12161
12164
  def group_by_contract(self, service_point_array): ...
12162
12165
  def imperex_get_values(self): ...
12163
12166
  def patch_asset(self, asset_id, payload, additional_headers=False): ...
12164
- def prepare_m2c_passthrough_payload(self, case_id, triplet, is_client=True, is_get_client_extended=False, is_validity_date=False, is_split_payment=False, is_contratti_mandato=False, service_points=False, service_points_billing_profile_array=False, is_associazioneContrattoFornitura=False, is_domiciliazione=False, is_ccr=False, is_dpay=False, contatti_array=False, extra_contract_info=False, extra_forniture_info=False, causale_attivazione=False, contrattiDettaglio_array=False, forn_contatti_array=False, contrattiMandatoDettaglio_array=False, is_cCodiceDestinatario=False, is_cCodiceUfficioPA=False, is_cigCup=False, is_contratti=False, is_prodotti=False, causale_cessazione=False, fine_contratto=False, fine_contratto_fornitura=False, is_oneri=False, charge=False, charge_type=False, charge_description=False, cma_array=False, is_codice_offerta=False, garanzia=False, is_contratti_mandato_dInizioValidita=False, protocollo=False, is_rcu=False, oneri_case_type=False, mask=None, is_storno=False, data_storno=False, storno_case_type=False): ...
12165
- def prepare_m2c_passthrough_payload__nightly(self, case_id, triplet, is_client=True, is_get_client_extended=False, is_validity_date=False, is_split_payment=False, is_contratti_mandato=False, service_points=False, service_points_billing_profile_array=False, is_associazioneContrattoFornitura=False, is_domiciliazione=False, is_ccr=False, is_dpay=False, contatti_array=False, extra_contract_info=False, extra_forniture_info=False, causale_attivazione=False, contrattiDettaglio_array=False, forn_contatti_array=False, contrattiMandatoDettaglio_array=False, is_cCodiceDestinatario=False, is_cCodiceUfficioPA=False, is_cigCup=False, is_contratti=False, is_prodotti=False, causale_cessazione=False, fine_contratto=False, fine_contratto_fornitura=False, is_oneri=False, charge=False, charge_type=False, charge_description=False, cma_array=False, is_codice_offerta=False, garanzia=False, is_contratti_mandato_dInizioValidita=False, protocollo=False, is_rcu=False, oneri_case_type=False, mask=None, is_storno=False, data_storno=False, storno_case_type=False): ...
12166
- def prepare_m2c_payload(self, case_id, triplet, is_client=True, is_get_client_extended=False, is_validity_date=False, is_split_payment=False, is_contratti_mandato=False, service_points=False, service_points_billing_profile_array=False, is_associazioneContrattoFornitura=False, is_domiciliazione=False, is_ccr=False, is_dpay=False, contatti_array=False, extra_contract_info=False, extra_forniture_info=False, causale_attivazione=False, contrattiDettaglio_array=False, forn_contatti_array=False, contrattiMandatoDettaglio_array=False, is_cCodiceDestinatario=False, is_cCodiceUfficioPA=False, is_cigCup=False, is_contratti=False, is_prodotti=False, causale_cessazione=False, is_oneri=False, charge=False, charge_type=False, charge_description=False, fine_contratto=False, fine_contratto_fornitura=False, cma_array=False, is_codice_offerta=False, garanzia=False, is_contratti_mandato_dInizioValidita=False, protocollo=False, is_rcu=False, oneri_case_type=False, isContrattiOLDAsset=False, is_storno=False, storno_case_type=False, data_storno=False, mask=None): ...
12167
+ def prepare_m2c_passthrough_payload(self, case_id, triplet, is_client=True, is_get_client_extended=False, is_validity_date=False, is_split_payment=False, is_contratti_mandato=False, service_points=False, service_points_billing_profile_array=False, is_associazioneContrattoFornitura=False, is_domiciliazione=False, is_ccr=False, is_dpay=False, contatti_array=False, extra_contract_info=False, extra_forniture_info=False, causale_attivazione=False, contrattiDettaglio_array=False, forn_contatti_array=False, contrattiMandatoDettaglio_array=False, is_cCodiceDestinatario=False, is_cCodiceUfficioPA=False, is_cigCup=False, is_contratti=False, is_prodotti=False, causale_cessazione=False, fine_contratto=False, fine_contratto_fornitura=False, is_oneri=False, charge=False, charge_type=False, charge_description=False, cma_array=False, is_codice_offerta=False, garanzia=False, is_contratti_mandato_dInizioValidita=False, protocollo=False, is_rcu=False, oneri_case_type=False, mask=None, is_storno=False, data_storno=False, storno_case_type=False, service_cell_index=False): ...
12168
+ def prepare_m2c_passthrough_payload__nightly(self, case_id, triplet, is_client=True, is_get_client_extended=False, is_validity_date=False, is_split_payment=False, is_contratti_mandato=False, service_points=False, service_points_billing_profile_array=False, is_associazioneContrattoFornitura=False, is_domiciliazione=False, is_ccr=False, is_dpay=False, contatti_array=False, extra_contract_info=False, extra_forniture_info=False, causale_attivazione=False, contrattiDettaglio_array=False, forn_contatti_array=False, contrattiMandatoDettaglio_array=False, is_cCodiceDestinatario=False, is_cCodiceUfficioPA=False, is_cigCup=False, is_contratti=False, is_prodotti=False, causale_cessazione=False, fine_contratto=False, fine_contratto_fornitura=False, is_oneri=False, charge=False, charge_type=False, charge_description=False, cma_array=False, is_codice_offerta=False, garanzia=False, is_contratti_mandato_dInizioValidita=False, protocollo=False, is_rcu=False, oneri_case_type=False, mask=None, is_storno=False, data_storno=False, storno_case_type=False, service_cell_index=False): ...
12169
+ def prepare_m2c_payload(self, case_id, triplet, is_client=True, is_get_client_extended=False, is_validity_date=False, is_split_payment=False, is_contratti_mandato=False, service_points=False, service_points_billing_profile_array=False, is_associazioneContrattoFornitura=False, is_domiciliazione=False, is_ccr=False, is_dpay=False, contatti_array=False, extra_contract_info=False, extra_forniture_info=False, causale_attivazione=False, contrattiDettaglio_array=False, forn_contatti_array=False, contrattiMandatoDettaglio_array=False, is_cCodiceDestinatario=False, is_cCodiceUfficioPA=False, is_cigCup=False, is_contratti=False, is_prodotti=False, causale_cessazione=False, is_oneri=False, charge=False, charge_type=False, charge_description=False, fine_contratto=False, fine_contratto_fornitura=False, cma_array=False, is_codice_offerta=False, garanzia=False, is_contratti_mandato_dInizioValidita=False, protocollo=False, is_rcu=False, oneri_case_type=False, isContrattiOLDAsset=False, is_storno=False, storno_case_type=False, data_storno=False, mask=None, cella=False): ...
12167
12170
  def process_attributes(self, family, supply_start_date, fonteprezzo): ...
12168
12171
  def process_child_products(self, components, supply_start_date): ...
12169
12172
  def process_family_attributes(self, families, supply_start_date, fonteprezzo): ...
@@ -14565,7 +14568,7 @@ class _M2cActivationDeactivationPayload(Recordset):
14565
14568
  def _M2CActivationDeactivationPayload__smart_patch(self, payload, key, value, on_missing='error'): ...
14566
14569
  def _check_valid_json(self): ...
14567
14570
  def get_payload_with_patch(self, patches=None, on_missing='error'): ...
14568
- def last(self, cod_pod, payload_type=None): ...
14571
+ def last(self, cod_pod, payload_type=None) -> "_M2cActivationDeactivationPayload": ...
14569
14572
 
14570
14573
  # --- mail.activity ---
14571
14574
 
@@ -16924,7 +16927,7 @@ class _MarketCommEventLog(Recordset):
16924
16927
  def exists(self) -> "_MarketCommEventLog": ...
16925
16928
  def sudo(self) -> "_MarketCommEventLog": ...
16926
16929
  def with_context(self, ctx: Dict[str, Any] = ..., **kwargs: Any) -> "_MarketCommEventLog": ...
16927
- def last(self, flow_code=None, service_code=None): ...
16930
+ def last(self, flow_code=None, service_code=None) -> "_MarketCommEventLog": ...
16928
16931
 
16929
16932
  # --- market.comm.event.log.cp.utente.filter ---
16930
16933
 
@@ -22540,7 +22543,7 @@ class _ResPartner(Recordset):
22540
22543
  def _avatar_get_placeholder_path(self): ...
22541
22544
  def _build_vat_error_message(self, country_code, wrong_vat, record_label): ...
22542
22545
  def _build_vcard(self): ...
22543
- def _calculate_service_cell(self): ...
22546
+ def _calculate_service_cell(self, trigger_type='all'): ...
22544
22547
  def _cf_check_birth_data(self, fiscal_code, gender, birth_date): ...
22545
22548
  def _cf_check_city(self, fiscal_code, birth_city): ...
22546
22549
  def _cf_check_name(self, fiscal_code, surname, firstname): ...
@@ -22652,7 +22655,10 @@ class _ResPartner(Recordset):
22652
22655
  def _display_address(self, without_company=False): ...
22653
22656
  def _display_address_depends(self): ...
22654
22657
  def _email_send(self, email_from, subject, body, on_error=None): ...
22655
- def _evaluate_service_cell_rules(self, target_cell, old_cell, sixty_days, thirteen_months): ...
22658
+ def _eval_cell_monthly_cron(self, target_cell, old_cell, thirteen_months): ...
22659
+ def _eval_cell_realtime(self, target_cell, old_cell): ...
22660
+ def _eval_cell_weekly_cron(self, target_cell, old_cell, sixty_days): ...
22661
+ def _evaluate_service_cell_rules(self, target_cell, old_cell, sixty_days, thirteen_months, trigger_type='all'): ...
22656
22662
  def _fields_sync(self, values): ...
22657
22663
  def _fields_view_get_address(self, arch): ...
22658
22664
  def _find_accounting_partner(self, partner): ...
@@ -22719,6 +22725,7 @@ class _ResPartner(Recordset):
22719
22725
  def _run_vat_test(self, vat_number, default_country, partner_is_company=True): ...
22720
22726
  def _send_to_m2c_passthrough(self): ...
22721
22727
  def _set_calendar_last_notif_ack(self): ...
22728
+ def _should_clear_mm(self, old_cell): ...
22722
22729
  def _signup_retrieve_partner(self, token, check_validity=False, raise_exception=False): ...
22723
22730
  def _sms_get_default_partners(self): ...
22724
22731
  def _split_vat(self, vat): ...
@@ -22804,6 +22811,7 @@ class _ResPartner(Recordset):
22804
22811
  def get_iva_rates(self): ...
22805
22812
  def get_mention_suggestions(self, search, limit=8, channel_id=None): ...
22806
22813
  def get_most_recent_termination_date(self): ...
22814
+ def get_order_sending_date(self, apply_filter=True): ...
22807
22815
  def get_pod_ids_from_service_point_ids(self): ...
22808
22816
  def get_selection_name_value(self, _lang_='it_IT'): ...
22809
22817
  def get_update_client_payload(self, new_status): ...
@@ -30217,6 +30225,7 @@ class _SympleInteraction(Recordset):
30217
30225
  def exists(self) -> "_SympleInteraction": ...
30218
30226
  def sudo(self) -> "_SympleInteraction": ...
30219
30227
  def with_context(self, ctx: Dict[str, Any] = ..., **kwargs: Any) -> "_SympleInteraction": ...
30228
+ def _check_complaint_start_date(self): ...
30220
30229
  def _check_multiple_contacts(self): ...
30221
30230
  def _check_tickets_before_close_interaction(self): ...
30222
30231
  def _checks_before_close_interaction(self): ...
@@ -30605,6 +30614,7 @@ class _SymplePbLauncher(Recordset):
30605
30614
  def _get_elements_eval_context(self, data, model, process, record, user): ...
30606
30615
  def get_event_html(self, res_model_name, res_id, process_key, instance_key): ...
30607
30616
  def get_html(self, model_name, record_id, data, user_id): ...
30617
+ def get_process_elements(self, process_name, model_name, record_id, data, user_id): ...
30608
30618
  def initialize_process(self, process_key, payload): ...
30609
30619
  def which(self, model_name): ...
30610
30620
 
@@ -31311,7 +31321,8 @@ class _SympleRcuLine(Recordset):
31311
31321
  def _inverse_cod_pdr(self): ...
31312
31322
  def _inverse_cod_pod(self): ...
31313
31323
  def action_change_state(self): ...
31314
- def action_check_cases(self): ...
31324
+ def action_check_activation_cases(self): ...
31325
+ def action_check_deactivation_cases(self): ...
31315
31326
  def action_create_case_when_no_deactivation_cases(self): ...
31316
31327
  def action_refresh_activation(self): ...
31317
31328
  def action_refresh_deactivation(self): ...
@@ -33806,12 +33817,14 @@ class _WelcomeAttachmentTable(Recordset):
33806
33817
  client_type: str
33807
33818
  commodity: str
33808
33819
  dms_attachment_code: str
33820
+ end_date: Optional[_dt.date]
33809
33821
  has_bit2publish_template: bool
33810
33822
  is_placet: bool
33811
33823
  is_unreachable: bool
33812
33824
  request_type: str
33813
33825
  sending_via: str
33814
33826
  show_bit2publish_button: bool
33827
+ start_date: Optional[_dt.date]
33815
33828
  table_type: str
33816
33829
  tension: str
33817
33830
  def browse(self, ids: Union[int, List[int]]) -> "_WelcomeAttachmentTable": ...
@@ -33829,31 +33842,6 @@ class _WelcomeAttachmentTable(Recordset):
33829
33842
  def sudo(self) -> "_WelcomeAttachmentTable": ...
33830
33843
  def with_context(self, ctx: Dict[str, Any] = ..., **kwargs: Any) -> "_WelcomeAttachmentTable": ...
33831
33844
 
33832
- # --- welcome.letter.attachment.mixin ---
33833
-
33834
- class _WelcomeLetterAttachmentMixin(Recordset):
33835
- bit2publish_template_ids: "_Bit2publishTemplate"
33836
- has_bit2publish_template: bool
33837
- show_bit2publish_button: bool
33838
- def browse(self, ids: Union[int, List[int]]) -> "_WelcomeLetterAttachmentMixin": ...
33839
- def search(
33840
- self,
33841
- domain: List[Any],
33842
- limit: Optional[int] = None,
33843
- order: Optional[str] = None,
33844
- offset: int = 0,
33845
- ) -> "_WelcomeLetterAttachmentMixin": ...
33846
- def create(self, vals: Dict[str, Any]) -> "_WelcomeLetterAttachmentMixin": ...
33847
- def filtered(self, func: Any) -> "_WelcomeLetterAttachmentMixin": ...
33848
- def sorted(self, key: Any = None, reverse: bool = False) -> "_WelcomeLetterAttachmentMixin": ...
33849
- def exists(self) -> "_WelcomeLetterAttachmentMixin": ...
33850
- def sudo(self) -> "_WelcomeLetterAttachmentMixin": ...
33851
- def with_context(self, ctx: Dict[str, Any] = ..., **kwargs: Any) -> "_WelcomeLetterAttachmentMixin": ...
33852
- def add_welcome_letter_attachment(self, attachment_codes): ...
33853
- def get_attachment_id_from_dms(self, path, attachment_name, b2w_url, b2w_token): ...
33854
- def get_welcome_letter_attachment_codes(self, event_id): ...
33855
- def welcome_letter_attachments(self, event_id): ...
33856
-
33857
33845
  # --- wizard.ir.model.menu.create ---
33858
33846
 
33859
33847
  class _WizardIrModelMenuCreate(Recordset):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waron97/prbot",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,10 +8,12 @@ import { fuzzyMatch } from '../lib/fuzzy.js';
8
8
  import { execGit } from '../lib/git.js';
9
9
  import {
10
10
  appendPrToLine,
11
+ appendRefsToLine,
11
12
  buildRefString,
12
13
  detectIndentation,
13
14
  extractSections,
14
15
  findDuplicateLine,
16
+ findLineByPrNumber,
15
17
  findSectionEndLine,
16
18
  } from './changelog.js';
17
19
 
@@ -70,6 +72,28 @@ function buildPrDescription(taskIds, jiras) {
70
72
  return lines.join('\n');
71
73
  }
72
74
 
75
+ async function fetchActivePr(branch) {
76
+ const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO } = process.env;
77
+ const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests?searchCriteria.sourceRefName=refs/heads/${branch}&searchCriteria.status=active&api-version=7.0`;
78
+ const res = await fetch(apiUrl, { headers: devopsHeaders() });
79
+ const data = await res.json();
80
+ if (!res.ok) throw new Error(`DevOps PR fetch failed: ${JSON.stringify(data)}`);
81
+ if (!data.value || data.value.length === 0) throw new Error(`No active PR found for branch: ${branch}`);
82
+ return data.value[0];
83
+ }
84
+
85
+ async function patchDevopsPrDescription(prId, description) {
86
+ const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO } = process.env;
87
+ const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests/${prId}?api-version=7.0`;
88
+ const res = await fetch(apiUrl, {
89
+ method: 'PATCH',
90
+ headers: devopsHeaders(),
91
+ body: JSON.stringify({ description }),
92
+ });
93
+ const data = await res.json();
94
+ if (!res.ok) throw new Error(`DevOps PR patch failed: ${JSON.stringify(data)}`);
95
+ }
96
+
73
97
  async function createDevopsPR(branch, title, description) {
74
98
  const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO, AUTOPR_TARGET_BRANCH } = process.env;
75
99
  const apiUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_apis/git/repositories/${DEVOPS_REPO}/pullrequests?api-version=7.0`;
@@ -190,7 +214,60 @@ async function selectSection(sections, candidates) {
190
214
  return sections.find((s) => s.heading === selected);
191
215
  }
192
216
 
217
+ async function autoprAmend(options) {
218
+ const ADDONS_PATH = resolveAddonsPath(process.env.ADDONS_PATH);
219
+ const changelogPath = `${ADDONS_PATH}/CHANGELOG.md`;
220
+ const { DEVOPS_ORG, DEVOPS_PROJECT, DEVOPS_REPO } = process.env;
221
+
222
+ const branch = (await execGit(['rev-parse', '--abbrev-ref', 'HEAD'], ADDONS_PATH)).trim();
223
+ const pr = await fetchActivePr(branch);
224
+ const prNumber = pr.pullRequestId;
225
+ const prUrl = `https://dev.azure.com/${DEVOPS_ORG}/${DEVOPS_PROJECT}/_git/${DEVOPS_REPO}/pullrequest/${prNumber}`;
226
+ console.log(`Found PR #${prNumber}: ${prUrl}`);
227
+
228
+ const ids = options.trident ?? [];
229
+ const jiras = options.jira ?? [];
230
+
231
+ const tasks = ids.length > 0 ? await Promise.all(ids.map(fetchTask)) : [];
232
+ tasks.forEach((t) => console.log(`Task: ${t.name}`));
233
+
234
+ const newLinks = [
235
+ ...ids.map((id) => `${process.env.TRIDENT_URL}/odoo/my-tasks/${id}`),
236
+ ...jiras.map((j) => `https://sorgenia.atlassian.net/browse/${j}`),
237
+ ];
238
+ if (newLinks.length > 0) {
239
+ const updatedDescription = pr.description
240
+ ? `${pr.description}\n${newLinks.join('\n')}`
241
+ : newLinks.join('\n');
242
+ await patchDevopsPrDescription(prNumber, updatedDescription);
243
+ console.log('PR description updated');
244
+ }
245
+
246
+ for (let i = 0; i < ids.length; i++) {
247
+ await appendChecklistPrLink(ids[i], tasks[i].x_release_checklist, prUrl, prNumber);
248
+ }
249
+ if (ids.length > 0) console.log('Checklist updated');
250
+
251
+ const content = readFileSync(changelogPath, 'utf-8');
252
+ const existing = findLineByPrNumber(content, prNumber);
253
+ if (!existing) {
254
+ console.log(`Warning: no changelog line found for PR #${prNumber} — skipping changelog update`);
255
+ return;
256
+ }
257
+ const lines = content.split('\n');
258
+ lines[existing.lineNumber] = appendRefsToLine(existing.line, ids, jiras);
259
+ await fs.writeFile(changelogPath, lines.join('\n'));
260
+
261
+ await execGit(['add', 'CHANGELOG.md'], ADDONS_PATH);
262
+ await execGit(['commit', '-m', '[DOC][CHANGELOG] Changelog'], ADDONS_PATH);
263
+ await execGit(['push'], ADDONS_PATH);
264
+ console.log('Changelog updated and pushed');
265
+ console.log('\nReminder: squash the two changelog commits before merging the PR.');
266
+ }
267
+
193
268
  async function autopr(options) {
269
+ if (options.amend) return autoprAmend(options);
270
+
194
271
  const ADDONS_PATH = resolveAddonsPath(process.env.ADDONS_PATH);
195
272
  const changelogPath = `${ADDONS_PATH}/CHANGELOG.md`;
196
273
 
@@ -179,6 +179,54 @@ async function changelog(prNumber, options) {
179
179
  console.log('Changelog entry added');
180
180
  }
181
181
 
182
+ function findLineByPrNumber(content, prNumber) {
183
+ const lines = content.split('\n');
184
+ for (let i = 0; i < lines.length; i++) {
185
+ if (lines[i].includes(`#${prNumber}ADO`)) {
186
+ return { lineNumber: i, line: lines[i] };
187
+ }
188
+ }
189
+ return null;
190
+ }
191
+
192
+ function appendRefsToLine(line, tridentIds, jiras) {
193
+ let result = line;
194
+
195
+ if (tridentIds && tridentIds.length > 0) {
196
+ const suffix = tridentIds.map((id) => `#${id}`).join(', ');
197
+ const tridentMatch = result.match(/Trident (#[\d, #]+)/);
198
+ if (tridentMatch) {
199
+ result = result.replace(/Trident (#[\d, #]+)/, `Trident ${tridentMatch[1]}, ${suffix}`);
200
+ } else {
201
+ const parenMatch = result.match(/\(([^)]*)\)\s*$/);
202
+ if (parenMatch) {
203
+ result = result.replace(/\(([^)]*)\)\s*$/, `(Trident ${suffix}, ${parenMatch[1]})`);
204
+ } else {
205
+ result = `${result.trimEnd()} (Trident ${suffix})`;
206
+ }
207
+ }
208
+ }
209
+
210
+ if (jiras && jiras.length > 0) {
211
+ const suffix = jiras.join(', ');
212
+ const jiraMatch = result.match(/JIRA ([^,)]+)/);
213
+ if (jiraMatch) {
214
+ result = result.replace(/JIRA ([^,)]+)/, `JIRA ${jiraMatch[1].trimEnd()}, ${suffix}`);
215
+ } else if (result.includes('PR sorgenia_addons')) {
216
+ result = result.replace(/(PR sorgenia_addons)/, `JIRA ${suffix}, $1`);
217
+ } else {
218
+ const parenMatch = result.match(/\(([^)]*)\)\s*$/);
219
+ if (parenMatch) {
220
+ result = result.replace(/\(([^)]*)\)\s*$/, `(${parenMatch[1]}, JIRA ${suffix})`);
221
+ } else {
222
+ result = `${result.trimEnd()} (JIRA ${suffix})`;
223
+ }
224
+ }
225
+ }
226
+
227
+ return result;
228
+ }
229
+
182
230
  export {
183
231
  changelog,
184
232
  extractSections,
@@ -187,4 +235,6 @@ export {
187
235
  buildRefString,
188
236
  findDuplicateLine,
189
237
  appendPrToLine,
238
+ findLineByPrNumber,
239
+ appendRefsToLine,
190
240
  };
@@ -2,10 +2,19 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import fetch from 'node-fetch';
4
4
  import search from '@inquirer/search';
5
+ import { select, confirm } from '@inquirer/prompts';
5
6
  import { getToken } from '../lib/auth.js';
6
7
  import { execGit } from '../lib/git.js';
7
8
  import { resolveAddonsPath } from '../lib/addons.js';
8
9
  import { fuzzyMatch } from '../lib/fuzzy.js';
10
+ import { log, isSilent } from '../lib/logger.js';
11
+ import { verbot } from './ver.js';
12
+ import {
13
+ readEmailTemplateMappings,
14
+ detectEmailRenames,
15
+ computeMigrationVersion,
16
+ generateEmailPreMigrateScript,
17
+ } from '../lib/premigrate.js';
9
18
 
10
19
  async function getWorkflows(token) {
11
20
  const url = `${process.env.RIP_URL}/symple.workflow/*`;
@@ -89,6 +98,21 @@ async function getModuleChoices() {
89
98
  .map((e) => ({ name: e.name, value: e.name }));
90
99
  }
91
100
 
101
+ async function resolveManifestPath(module, ADDONS_PATH) {
102
+ for (const candidate of [
103
+ path.join(ADDONS_PATH, module, '__manifest__.py'),
104
+ path.join(ADDONS_PATH, 'config', module, '__manifest__.py'),
105
+ ]) {
106
+ try {
107
+ await fs.access(candidate);
108
+ return candidate;
109
+ } catch {
110
+ // try next
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+
92
116
  async function exportEmailTemplates(opts) {
93
117
  const token = await getToken();
94
118
 
@@ -104,7 +128,7 @@ async function exportEmailTemplates(opts) {
104
128
  },
105
129
  });
106
130
 
107
- console.log('Fetching workflows...');
131
+ log('Fetching workflows...');
108
132
  const workflows = await getWorkflows(token);
109
133
  const choices = workflows.map((w) => ({ name: w.name, value: w.id }));
110
134
 
@@ -121,7 +145,7 @@ async function exportEmailTemplates(opts) {
121
145
  },
122
146
  });
123
147
 
124
- console.log(`Fetching email templates for workflow ${workflowId}...`);
148
+ log(`Fetching email templates for workflow ${workflowId}...`);
125
149
  const excludes = opts.exclude ?? [];
126
150
  const templates = (await getEmailTemplates(workflowId, token))
127
151
  .filter((t) => t.template_code)
@@ -130,7 +154,7 @@ async function exportEmailTemplates(opts) {
130
154
  });
131
155
 
132
156
  if (!templates.length) {
133
- console.log('No email templates found for this workflow.');
157
+ log('No email templates found for this workflow.');
134
158
  return;
135
159
  }
136
160
 
@@ -138,17 +162,75 @@ async function exportEmailTemplates(opts) {
138
162
  const dataDir = path.join(ADDONS_PATH, 'config', module, 'data');
139
163
  await fs.mkdir(dataDir, { recursive: true });
140
164
 
165
+ const oldMappings = await readEmailTemplateMappings(dataDir);
166
+
141
167
  const outPath = path.join(dataDir, 'mail_template.xml');
142
168
  await fs.writeFile(outPath, generateXml(templates), 'utf-8');
143
- console.log(`Written: ${outPath}`);
144
-
145
- if (opts.commit !== false) {
146
- await execGit(['add', outPath], ADDONS_PATH);
147
- await execGit(
148
- ['commit', '-m', `[IMP][${module}] Export email templates`],
149
- ADDONS_PATH,
150
- );
151
- console.log('Committed.');
169
+ log(`Written: ${outPath}`);
170
+
171
+ const newMappings = await readEmailTemplateMappings(dataDir);
172
+ const renames = detectEmailRenames(oldMappings, newMappings);
173
+
174
+ let bumpLevel = opts.bump;
175
+ if (!bumpLevel) {
176
+ bumpLevel = await select({
177
+ message: 'Bump version?',
178
+ choices: [
179
+ { name: 'No bump', value: 'none' },
180
+ { name: 'Patch', value: 'patch' },
181
+ { name: 'Minor', value: 'minor' },
182
+ { name: 'Major', value: 'major' },
183
+ ],
184
+ });
185
+ }
186
+
187
+ let preMigratePath = null;
188
+
189
+ if (renames.length > 0) {
190
+ log(`Renamed XML IDs (${renames.length}): ${renames.map((r) => `${r.oldXmlId} → ${r.newXmlId}`).join(', ')}`);
191
+
192
+ let shouldGenerate = opts.autoPremigrate;
193
+ if (!shouldGenerate && !isSilent()) {
194
+ shouldGenerate = await confirm({
195
+ message: `Detected ${renames.length} renamed template_code(s). Generate pre-migrate script?`,
196
+ default: true,
197
+ });
198
+ }
199
+
200
+ if (shouldGenerate) {
201
+ const manifestPath = await resolveManifestPath(module, ADDONS_PATH);
202
+ if (!manifestPath) {
203
+ log(`Warning: __manifest__.py not found for ${module}, skipping pre-migrate generation`);
204
+ } else {
205
+ const version = await computeMigrationVersion(manifestPath, bumpLevel);
206
+ const migrationDir = path.join(ADDONS_PATH, 'config', module, 'migrations', version);
207
+ preMigratePath = path.join(migrationDir, 'pre-migrate.py');
208
+ await fs.mkdir(migrationDir, { recursive: true });
209
+ await fs.writeFile(preMigratePath, generateEmailPreMigrateScript(renames));
210
+ log(`Wrote pre-migrate: ${preMigratePath}`);
211
+ }
212
+ }
213
+ }
214
+
215
+ if (opts.commit === false) {
216
+ if (bumpLevel && bumpLevel !== 'none') {
217
+ await verbot(module, bumpLevel, { ...opts, commit: false });
218
+ }
219
+ return;
220
+ }
221
+
222
+ const filesToAdd = [outPath];
223
+ if (preMigratePath) filesToAdd.push(preMigratePath);
224
+
225
+ for (const filePath of filesToAdd) {
226
+ await execGit(['add', filePath], ADDONS_PATH);
227
+ }
228
+
229
+ await execGit(['commit', '-m', `[IMP][${module}] Export email templates`], ADDONS_PATH);
230
+ log('Committed.');
231
+
232
+ if (bumpLevel && bumpLevel !== 'none') {
233
+ await verbot(module, bumpLevel, opts);
152
234
  }
153
235
  }
154
236
 
@@ -6,6 +6,7 @@ import { getToken } from '../lib/auth.js';
6
6
  import { execGit } from '../lib/git.js';
7
7
  import { resolveAddonsPath } from '../lib/addons.js';
8
8
  import { fuzzyMatch } from '../lib/fuzzy.js';
9
+ import { log } from '../lib/logger.js';
9
10
 
10
11
  const IMPEREX_REL = 'sorgenia_imperex_metadata/migrations/0.0.0/imperex';
11
12
 
@@ -56,7 +57,7 @@ async function exportImperex(opts) {
56
57
  },
57
58
  });
58
59
 
59
- console.log(`Fetching records for ${model}...`);
60
+ log(`Fetching records for ${model}...`);
60
61
  const records = await listRecords(model, token);
61
62
  const recChoices = records.map((r) => ({ name: String(r.name ?? r.id), value: r.id }));
62
63
  const recordId = await search({
@@ -67,7 +68,7 @@ async function exportImperex(opts) {
67
68
  },
68
69
  });
69
70
 
70
- console.log(`Exporting record ${recordId}...`);
71
+ log(`Exporting record ${recordId}...`);
71
72
  const { attachments } = await exportRecord(model, recordId, token);
72
73
 
73
74
  const modelDir = path.join(ADDONS_PATH, IMPEREX_REL, model);
@@ -78,7 +79,7 @@ async function exportImperex(opts) {
78
79
  if (att.name === '__manifest__.yaml') continue;
79
80
  const destPath = path.join(modelDir, path.basename(att.name));
80
81
  await fs.writeFile(destPath, att.content, 'utf-8');
81
- console.log(`Written: ${destPath}`);
82
+ log(`Written: ${destPath}`);
82
83
  saved.push(destPath);
83
84
  }
84
85
 
@@ -90,7 +91,7 @@ async function exportImperex(opts) {
90
91
  ['commit', '-m', `[IMP][sorgenia_imperex_metadata] update ${model} record`],
91
92
  ADDONS_PATH,
92
93
  );
93
- console.log('Committed.');
94
+ log('Committed.');
94
95
  }
95
96
  }
96
97