From d5414146cbbfe2e8301331069f598e77adfb64f2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Feb 2024 13:34:18 +0530 Subject: [PATCH 01/79] build: pin frappe & erpnext requirements to ^15.0.0 (#1460) --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 84e452a461..d927c4383f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,4 +26,8 @@ indent = "\t" known_frappe = ["frappe"] known_erpnext = ["erpnext"] known_hrms = ["hrms"] -sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FRAPPE", "ERPNEXT", "HRMS", "FIRSTPARTY", "LOCALFOLDER"] \ No newline at end of file +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FRAPPE", "ERPNEXT", "HRMS", "FIRSTPARTY", "LOCALFOLDER"] + +[tool.bench.frappe-dependencies] +frappe = ">=15.0.0,<16.0.0" +erpnext = ">=15.0.0,<16.0.0" \ No newline at end of file From bf53ed88262ccbcaedf9bbc11986d09679f31e4a Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:42:09 +0100 Subject: [PATCH 02/79] fix: german translations (#1463) With focus on Leaves and Holidays --- hrms/translations/de.csv | 142 +++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/hrms/translations/de.csv b/hrms/translations/de.csv index c78ea58805..92c2411950 100644 --- a/hrms/translations/de.csv +++ b/hrms/translations/de.csv @@ -5,10 +5,10 @@ Accrual Journal Entry for salaries from {0} to {1},Abgrenzungsjournalbuchung fü Add to Details,Zu Details hinzufügen, Added to details,Zu Details hinzugefügt, All Jobs,Alle Jobs, -Allocated Leaves,Zugewiesene Urlaubstage, +Allocated Leaves,Zugewiesene Abwesenheiten, Annual Salary,Jahresgehalt, -Application period cannot be across two allocation records,Der Bewerbungszeitraum kann nicht über zwei Zuordnungssätze liegen, -Application period cannot be outside leave allocation period,Beantragter Zeitraum kann nicht außerhalb der beantragten Urlaubszeit liegen, +Application period cannot be across two allocation records,"Der beantragte Zeitraum kann sich nicht über zwei Zuteilungen erstrecken", +Application period cannot be outside leave allocation period,"Der beantragter Zeitraum kann nicht außerhalb des Zeitraums liegen, für den das Abwesenheitskontingent gewährt wurde", Apply Now,Jetzt bewerben, Apprentice,Auszubildende(r), Approval Status,Genehmigungsstatus, @@ -57,7 +57,7 @@ Employee relieved on {0} must be set as 'Left',"Freigestellter Angestellter {0} Employee {0} already submited an apllication {1} for the payroll period {2},Mitarbeiter {0} hat bereits eine Bewerbung {1} für die Abrechnungsperiode {2} eingereicht, Employee {0} has no maximum benefit amount,Der Mitarbeiter {0} hat keinen maximalen Leistungsbetrag, Employee {0} is not active or does not exist,Mitarbeiter {0} ist nicht aktiv oder existiert nicht, -Employee {0} is on Leave on {1},Mitarbeiter {0} ist auf Urlaub auf {1}, +Employee {0} is on Leave on {1},Mitarbeiter {0} ist am {1} abwesend, Employee {0} on Half day on {1},Mitarbeiter {0} am {1} nur halbtags anwesend, End time cannot be before start time,Die Endzeit darf nicht vor der Startzeit liegen, Evaluation,Beurteilung, @@ -95,20 +95,20 @@ Intern,Praktikant, Job Description,Tätigkeitsbeschreibung, Job Offer,Jobangebot, Jobs,freie Stellen, -Leave Approval Notification,Benachrichtigung über neuen Urlaubsantrag, -Leave Blocked,Urlaub gesperrt, +Leave Approval Notification,Benachrichtigung über neuen Abwesenheitsantrag, +Leave Blocked,Abwesenheit gesperrt, Leave Encashment,Einlösung gewähren, -Leave Status Notification,Benachrichtigung über den Status des Urlaubsantrags, -Leave Type,Urlaubstyp, -Leave Type is madatory,Urlaubsart ist Pflicht, -Leave Type {0} cannot be allocated since it is leave without pay,"Urlaubstyp {0} kann nicht zugeordnet werden, da unbezahlter Urlaub.", -Leave Type {0} cannot be carry-forwarded,Urlaubstyp {0} kann nicht in die Zukunft übertragen werden, +Leave Status Notification,Benachrichtigung über den Status des Abwesenheitsantrags, +Leave Type,Abwesenheitsart, +Leave Type is madatory,Abwesenheitsart ist obligatorisch, +Leave Type {0} cannot be allocated since it is leave without pay,"Abwesenheitsart {0} kann nicht zugeordnet werden, da unbezahlt.", +Leave Type {0} cannot be carry-forwarded,Abwesenheitsart {0} kann nicht in die Zukunft übertragen werden, Leave Type {0} is not encashable,Abwesenheitsart {0} ist nicht umsetzbar, Leave Without Pay,Unbezahlter Urlaub, -"Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Resturlaub bereits in den zukünftigen Datensatz für Urlaube {1} übertragen wurde, kann der Urlaub nicht vor {0} zugeteilt werden.", -"Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Resturlaub bereits in den zukünftigen Datensatz für Urlaube {1} übertragen wurde, kann der Urlaub nicht vor {0} genehmigt/abgelehnt werden.", +"Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Resturlaub bereits in zukünftige Abwesenheitskontingente {1} übertragen wurde, kann der Urlaub nicht vor {0} zugeteilt werden.", +"Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Resturlaub bereits in zukünftige Abwesenheitskontingente {1} übertragen wurde, kann der Urlaub nicht vor {0} genehmigt/abgelehnt werden.", Leave of type {0} cannot be longer than {1},Abwesenheit vom Typ {0} kann nicht länger sein als {1}, -Leaves,Blätter, +Leaves,Abwesenheiten, Leaves Allocated Successfully for {0},Erfolgreich zugewiesene Abwesenheiten für {0}, Leaves per Year,Abwesenheiten pro Jahr, Lifecycle,Lebenszyklus, @@ -118,7 +118,7 @@ Max benefits should be greater than zero to dispense benefits,"Der maximale Nutz Maximum amount eligible for the component {0} exceeds {1},Der für die Komponente {0} zulässige Höchstbetrag übersteigt {1}, Maximum benefit amount of component {0} exceeds {1},Der maximale Leistungsbetrag der Komponente {0} übersteigt {1}, Maximum benefit amount of employee {0} exceeds {1},Der maximale Leistungsbetrag von Mitarbeiter {0} übersteigt {1}, -Maximum leave allowed in the leave type {0} is {1},Der maximal zulässige Urlaub im Urlaubstyp {0} ist {1}, +Maximum leave allowed in the leave type {0} is {1},Die maximal zulässige Abwesenheit für Abwesenheitsart {0} ist {1}, Medical,Medizinisch, Mode of payment is required to make a payment,"Modus der Zahlung ist erforderlich, um eine Zahlung zu leisten", More than one selection for {0} not allowed,Mehr als eine Auswahl für {0} ist nicht zulässig, @@ -135,7 +135,7 @@ No replies from,Keine Antworten, No salary slip found to submit for the above selected criteria OR salary slip already submitted,Es wurde kein Lohnzettel für die oben ausgewählten Kriterien oder den bereits eingereichten Gehaltsbeleg gefunden, Nothing to change,Nichts zu ändern, Notice Period,Mitteilungsfrist, -Only Leave Applications with status 'Approved' and 'Rejected' can be submitted,"Nur Urlaubsanträge mit dem Status ""Gewährt"" und ""Abgelehnt"" können übermittelt werden.", +Only Leave Applications with status 'Approved' and 'Rejected' can be submitted,"Nur Abwesenheitsanträge mit dem Status ""Gewährt"" und ""Abgelehnt"" können übermittelt werden.", Optional Holiday List not set for leave period {0},Optionale Feiertagsliste ist für Abwesenheitszeitraum {0} nicht festgelegt, Part-time,Teilzeit, Password policy for Salary Slips is not set,Die Kennwortrichtlinie für Gehaltsabrechnungen ist nicht festgelegt, @@ -150,8 +150,8 @@ Please enable default incoming account before creating Daily Work Summary Group, Please select Company and Designation,Bitte wählen Sie Unternehmen und Position, Please select Employee,Bitte wählen Sie Mitarbeiter, Please select a csv file,Bitte eine CSV-Datei auswählen., -Please set default template for Leave Approval Notification in HR Settings.,Bitte legen Sie eine Email-Vorlage für Benachrichtigung über neuen Urlaubsantrag in den HR-Einstellungen fest., -Please set default template for Leave Status Notification in HR Settings.,Bitte legen Sie die Email-Vorlage für Statusänderung eines Urlaubsantrags in den HR-Einstellungen fest., +Please set default template for Leave Approval Notification in HR Settings.,Bitte legen Sie eine Email-Vorlage für Benachrichtigung über neuen Abwesenheitsantrag in den HR-Einstellungen fest., +Please set default template for Leave Status Notification in HR Settings.,Bitte legen Sie die Email-Vorlage für Statusänderung eines Abwesenheitsantrags in den HR-Einstellungen fest., Please set the Company,Bitte setzen Sie das Unternehmen, Please set the Date Of Joining for employee {0},Bitte setzen Sie das Datum des Beitritts für Mitarbeiter {0}, Please share your feedback to the training by clicking on 'Training Feedback' and then 'New',"Bitte teilen Sie Ihr Feedback mit dem Training ab, indem Sie auf 'Training Feedback' und dann 'New' klicken.", @@ -196,7 +196,7 @@ Submit this to create the Employee record,"Übergeben Sie dies, um den Mitarbeit Submitting Salary Slips...,Lohnzettel einreichen ..., Team Updates,Team-Updates, Thank you,Danke, -The day(s) on which you are applying for leave are holidays. You need not apply for leave.,"Der Tag/die Tage, für den/die Sie Urlaub beantragen, sind Ferien. Deshalb müssen Sie keinen Urlaub beantragen.", +The day(s) on which you are applying for leave are holidays. You need not apply for leave.,"Die Tage, für die Sie Urlaub beantragen, sind arbeitsfreie Tage. Deshalb müssen Sie keinen Urlaub beantragen.", There are more holidays than working days this month.,Es gibt mehr Feiertage als Arbeitstage in diesem Monat., There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1}, This is based on the attendance of this Employee,Dies hängt von der Anwesenheit dieses Mitarbeiters ab, @@ -204,13 +204,13 @@ This will submit Salary Slips and create accrual Journal Entry. Do you want to p To date can not be equal or less than from date,Bis heute kann nicht gleich oder weniger als von Datum sein, To date can not be less than from date,Bis heute kann nicht weniger als von Datum sein, To date can not greater than employee's relieving date,Bis heute kann nicht mehr als Entlastungsdatum des Mitarbeiters sein, -Total Absent,Summe Abwesenheit, +Total Absent,Summe Fehltage, Total Deduction,Gesamtabzug, -Total Leaves,insgesamt Blätter, -Total Present,Summe Anwesend, +Total Leaves,Summe Abwesenheiten, +Total Present,Summe Anwesenheiten, Total advance amount cannot be greater than total sanctioned amount,Der gesamte Vorschussbetrag darf nicht höher sein als der Gesamtbetrag der Sanktion, Total flexible benefit component amount {0} should not be less than max benefits {1},Der Gesamtbetrag der flexiblen Leistungskomponente {0} sollte nicht unter dem Höchstbetrag der Leistungen {1} liegen., -Total leaves allocated is mandatory for Leave Type {0},Die Gesamtzahl der zugewiesenen Blätter ist für Abwesenheitsart {0} erforderlich., +Total leaves allocated is mandatory for Leave Type {0},Die Gesamtzahl der zugewiesenen Abwesenheiten ist für Abwesenheitsart {0} erforderlich., Total working hours should not be greater than max working hours {0},Insgesamt Arbeitszeit sollte nicht größer sein als die maximale Arbeitszeit {0}, Training,Ausbildung, Training Event,Schulungsveranstaltung, @@ -250,12 +250,12 @@ Event Link,Ereignis-Link, Expire Allocation,Verfall Zuteilung, Fuel Expense,Treibstoffkosten, Intermediate,Mittlere, -Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay,Der Urlaubsantrag ist mit den Urlaubszuteilungen {0} verknüpft. Urlaubsantrag kann nicht als bezahlter Urlaub festgelegt werden, +Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay,Der Abwesenheitsantrag ist mit den Abwesenheitskontingent {0} verknüpft. Abwesenheitsantrag kann nicht als bezahlt festgelegt werden, Log Type is required for check-ins falling in the shift: {0}.,Der Protokolltyp ist für Eincheckvorgänge in der Schicht erforderlich: {0}., No Employee found for the given employee field value. '{}': {},Für den angegebenen Mitarbeiterfeldwert wurde kein Mitarbeiter gefunden. '{}': {}, -No Leaves Allocated to Employee: {0} for Leave Type: {1},Keine Blätter dem Mitarbeiter zugewiesen: {0} für Urlaubstyp: {1}, +No Leaves Allocated to Employee: {0} for Leave Type: {1},Keine Abwesenheiten dem Mitarbeiter zugewiesen: {0} für Abwesenheitsart: {1}, Only expired allocation can be cancelled,Nur abgelaufene Zuordnungen können storniert werden, -Only users with the {0} role can create backdated leave applications,Nur Benutzer mit der Rolle {0} können zurückliegende Urlaubsanträge erstellen, +Only users with the {0} role can create backdated leave applications,Nur Benutzer mit der Rolle {0} können zurückdatierte Abwesenheitsanträge erstellen, Password policy cannot contain spaces or simultaneous hyphens. The format will be restructured automatically,Die Kennwortrichtlinie darf keine Leerzeichen oder Bindestriche gleichzeitig enthalten. Das Format wird automatisch umstrukturiert, Please enter the designation,Bitte geben Sie die Position ein, There are no vacancies under staffing plan {0},Es gibt keine offenen Stellen im Besetzungsplan {0}, @@ -276,12 +276,12 @@ Please setup numbering series for Attendance via Setup > Numbering Series,Bitte Current Odometer Value should be greater than Last Odometer Value {0},Der aktuelle Kilometerzählerwert sollte größer sein als der letzte Kilometerzählerwert {0}., No additional expenses has been added,Es wurden keine zusätzlichen Kosten hinzugefügt, {0} already exists for employee {1} and period {2},{0} existiert bereits für Mitarbeiter {1} und Periode {2}, -Leaves Allocated,Blätter zugeordnet, -Leave Without Pay does not match with approved {} records,Urlaub ohne Bezahlung stimmt nicht mit genehmigten {} Datensätzen überein, +Leaves Allocated,Abwesnheiten zugeordnet, +Leave Without Pay does not match with approved {} records,Unbezahlter Urlaub stimmt nicht mit genehmigten {} Datensätzen überein, Income Tax Slab not set in Salary Structure Assignment: {0},Einkommensteuerplatte nicht in Gehaltsstrukturzuordnung festgelegt: {0}, Income Tax Slab: {0} is disabled,Einkommensteuerplatte: {0} ist deaktiviert, Income Tax Slab must be effective on or before Payroll Period Start Date: {0},Die Einkommensteuerplatte muss am oder vor dem Startdatum der Abrechnungsperiode wirksam sein: {0}, -No leave record found for employee {0} on {1},Für Mitarbeiter {0} auf {1} wurde kein Urlaubsdatensatz gefunden, +No leave record found for employee {0} on {1},Für Mitarbeiter {0} am {1} wurde kein Abwesenheitsdatensatz gefunden, Row {0}: {1} is required in the expenses table to book an expense claim.,"Zeile {0}: {1} in der Tabelle der Auslagen ist erforderlich, um eine Auslagenabrechnung zu buchen.", Set the default account for the {0} {1},Legen Sie das Standardkonto für {0} {1} fest, (Half Day),(Halber Tag), @@ -326,9 +326,9 @@ Appraisal Template Goal,Bewertungsvorlage zur Zielorientierung, KRA,KRA, Key Performance Area,Entscheidender Leistungsbereich, HR-ATT-.YYYY.-,HR-ATT-.YYYY.-.-, -On Leave,Im Urlaub, +On Leave,Abwesend, Work From Home,Von zuhause aus arbeiten, -Leave Application,Urlaubsantrag, +Leave Application,Abwesenheitsantrag, Attendance Date,Anwesenheitsdatum, Attendance Request,Anwesenheitsanfrage, Late Entry,Späte Einreise, @@ -337,8 +337,8 @@ Half Day Date,Halbtagesdatum, On Duty,Im Dienst, Explanation,Erklärung, Compensatory Leave Request,Ausgleichsurlaubsantrag, -Leave Allocation,Urlaubszuordnung, -Worked On Holiday,Im Urlaub gearbeitet, +Leave Allocation,Abwesenheitskontingent, +Worked On Holiday,An arbeitsfreiem Tag gearbeitet, Work From Date,Arbeit von Datum, Work End Date,Arbeitsenddatum, Email Sent To,Email an gesendet, @@ -459,7 +459,7 @@ Expense Approver Mandatory In Expense Claim,Auslagengenehmiger in Auslagenabrech Payroll Settings,Einstellungen zur Gehaltsabrechnung, Leave,Verlassen, Max working hours against Timesheet,Max Arbeitszeit gegen Stundenzettel, -Include holidays in Total no. of Working Days,Urlaub in die Gesamtzahl der Arbeitstage mit einbeziehen, +Include holidays in Total no. of Working Days,Arbeitsfreie Tage in die Gesamtzahl der Arbeitstage mit einbeziehen, "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day","Falls diese Option aktiviert ist, beinhaltet die Gesamtanzahl der Arbeitstage auch Feiertage und der Wert ""Gehalt pro Tag"" wird reduziert", "If checked, hides and disables Rounded Total field in Salary Slips","Wenn diese Option aktiviert ist, wird das Feld "Gerundete Summe" in Gehaltsabrechnungen ausgeblendet und deaktiviert", The fraction of daily wages to be paid for half-day attendance,"Der Bruchteil des Tageslohns, der für die halbtägige Anwesenheit zu zahlen ist", @@ -469,9 +469,9 @@ Encrypt Salary Slips in Emails,Gehaltsabrechnungen in E-Mails verschlüsseln, "The salary slip emailed to the employee will be password protected, the password will be generated based on the password policy.","Die Gehaltsabrechnung, die per E-Mail an den Mitarbeiter gesendet wird, ist passwortgeschützt. Das Passwort wird basierend auf der Passwortrichtlinie generiert.", Password Policy,Kennwortrichtlinie, Example: SAL-{first_name}-{date_of_birth.year}
This will generate a password like SAL-Jane-1972,Beispiel: SAL- {Vorname} - {Geburtsdatum.Jahr}
Dies erzeugt ein Passwort wie SAL-Jane-1972, -Leave Approval Notification Template,Email-Vorlage für Benachrichtigung über neuen Urlaubsantrag, -Leave Status Notification Template,Email-Vorlage für Statusänderung eines Urlaubsantrags, -Role Allowed to Create Backdated Leave Application,Berechtigte Rolle zum Erstellen eines zurückdatierten Urlaubsantrags, +Leave Approval Notification Template,Email-Vorlage für Benachrichtigung über neuen Abwesenheitsantrag, +Leave Status Notification Template,Email-Vorlage für Statusänderung eines Abwesenheitsantrags, +Role Allowed to Create Backdated Leave Application,Berechtigte Rolle zum Erstellen eines zurückdatierten Abwesenheitsantrags, Leave Approver Mandatory In Leave Application,Berechtigungsauslöser in Abwesenheitsanwendung auslassen, Show Leaves Of All Department Members In Calendar,Abwesenheiten aller Abteilungsmitglieder im Kalender anzeigen, Auto Leave Encashment,Automatisches Verlassen der Einlösung, @@ -505,32 +505,32 @@ Staffing Plan,Personalplanung, Planned number of Positions,Geplante Anzahl von Positionen, "Job profile, qualifications required etc.","Stellenbeschreibung, erforderliche Qualifikationen usw.", HR-LAL-.YYYY.-,HR-LAL-.YYYY.-, -New Leaves Allocated,Neue Urlaubszuordnung, -Add unused leaves from previous allocations,Ungenutzten Urlaub von vorherigen Zuteilungen hinzufügen, -Unused leaves,Ungenutzter Urlaub, -Total Leaves Allocated,Insgesamt zugewiesene Urlaubstage, -Total Leaves Encashed,Insgesamt Blätter umkränzt, +New Leaves Allocated,Neues Abwesenheitskontingent, +Add unused leaves from previous allocations,Ungenutzte Abwesenheiten aus vorherigen Kontingenten hinzufügen, +Unused leaves,Ungenutzte Abwesenheiten, +Total Leaves Allocated,Insgesamt zugewiesene Abwesenheiten, +Total Leaves Encashed,Insgesamt ausbezahlte Abwesenheiten, Leave Period,Urlaubszeitraum, -Carry Forwarded Leaves,Übertragene Urlaubsgenehmigungen, -Apply / Approve Leaves,Urlaub eintragen/genehmigen, +Carry Forwarded Leaves,Übertragene Abwesenheitstage, +Apply / Approve Leaves,Abwesenheiten beantragen/genehmigen, HR-LAP-.YYYY.-,HR-LAP-.YYYY.-, -Leave Balance Before Application,Urlaubstage vor Antrag, -Total Leave Days,Urlaubstage insgesamt, -Leave Approver Name,Name des Urlaubsgenehmigers, +Leave Balance Before Application,Abwesenheitstage vor Antrag, +Total Leave Days,Abwesenheitstage insgesamt, +Leave Approver Name,Name des Genehmigers, Follow via Email,Per E-Mail nachverfolgen, -Block Holidays on important days.,Urlaub an wichtigen Tagen sperren., -Leave Block List Name,Name der Urlaubssperrenliste, +Block Holidays on important days.,Abwesenheit an wichtigen Tagen sperren., +Leave Block List Name,Name der Abwesenheitssperrliste, Applies to Company,Gilt für Unternehmen, "If not checked, the list will have to be added to each Department where it has to be applied.","Wenn deaktiviert, muss die Liste zu jeder Abteilung, für die sie gelten soll, hinzugefügt werden.", Block Days,Tage sperren, Stop users from making Leave Applications on following days.,"Benutzer davon abhalten, Urlaubsanträge für folgende Tage einzureichen.", -Leave Block List Dates,Urlaubssperrenliste Termine, +Leave Block List Dates,Abwesenheitssperrliste Termine, Allow Users,Benutzer zulassen, Allow the following users to approve Leave Applications for block days.,"Zulassen, dass die folgenden Benutzer Urlaubsanträge für Blöcke von Tagen genehmigen können.", -Leave Block List Allowed,Urlaubssperrenliste zugelassen, -Leave Block List Allow,Urlaubssperrenliste zulassen, +Leave Block List Allowed,Abwesenheitssperrliste zugelassen, +Leave Block List Allow,Abwesenheitssperrliste zulassen, Allow User,Benutzer zulassen, -Leave Block List Date,Urlaubssperrenliste Datum, +Leave Block List Date,Abwesenheitssperrliste Datum, Block Date,Datum sperren, Leave Control Panel,Urlaubsverwaltung, Employment Type (optional),Anstellungsart (optional), @@ -539,10 +539,10 @@ Department (optional),Abteilung (optional), Designation (optional),Position (optional), Employee Grade (optional),Dienstgrad (optional), Employee (optional),Mitarbeiter (optional), -Allocate Leaves,Blätter zuweisen, +Allocate Leaves,Abwesenheiten zuweisen, Carry Forward,Übertragen, Please select Carry Forward if you also want to include previous fiscal year's balance leaves to this fiscal year,"Bitte auf ""Übertragen"" klicken, wenn auch die Abwesenheitskonten des vorangegangenen Geschäftsjahrs in dieses Geschäftsjahr einbezogen werden sollen", -New Leaves Allocated (In Days),Neue Urlaubszuordnung (in Tagen), +New Leaves Allocated (In Days),Neue zugeordnete Abwesenheiten (in Tagen), Leave Balance,Balance verlassen, Encashable days,Bezwingbare Tage, Encashment Amount,Einzahlungsbetrag, @@ -550,9 +550,9 @@ Leave Ledger Entry,Ledger-Eintrag verlassen, Transaction Name,Transaktionsname, Is Carry Forward,Ist Übertrag, Is Expired,Ist abgelaufen, -Is Leave Without Pay,Ist unbezahlter Urlaub, -Holiday List for Optional Leave,Feiertagsliste für optionalen Urlaub, -Leave Allocations,Zuteilungen verlassen, +Is Leave Without Pay,Ist unbezahlt, +Holiday List for Optional Leave,Feiertagsliste für optionale Abwesenheit, +Leave Allocations,Abwesenheitskontingente, Leave Policy Details,Urlaubsrichtliniendetails, Leave Policy Detail,Urlaubsrichtliniendetail, Annual Allocation,Jährliche Zuteilung, @@ -560,16 +560,16 @@ Leave Type Name,Bezeichnung der Abwesenheit, Applicable After (Working Days),Anwendbar nach (Werktagen), Is Optional Leave,Ist optional verlassen, Allow Negative Balance,Negativen Saldo zulassen, -Include holidays within leaves as leaves,Urlaube innerhalb von Abwesenheiten als Abwesenheiten mit einbeziehen, +Include holidays within leaves as leaves,Arbeitsfreie Tage innerhalb von Abwesenheiten vom Kontingent abziehen, Is Compensatory,Ist kompensatorisch, -Maximum Carry Forwarded Leaves,Maximale Anzahl weitergeleiteter Blätter, -Expire Carry Forwarded Leaves (Days),Verfallsdatum für weitergeleitete Blätter (Tage), +Maximum Carry Forwarded Leaves,Maximale Anzahl übertragener Abewenheiten, +Expire Carry Forwarded Leaves (Days),Verfallsdatum für übertragene Abewenheiten (Tage), Calculated in days,Berechnet in Tagen, Encashment,Einlösung, Allow Encashment,Erlaube zulassen, Encashment Threshold Days,Einzahlungsschwellentage, Earned Leave,Verdienter Urlaub, -Is Earned Leave,Ist verdient Urlaub, +Is Earned Leave,Ist verdienter Urlaub, Earned Leave Frequency,Verdiente Austrittsfrequenz, Rounding,Rundung, Payroll Employee Detail,Personalabrechnung Mitarbeiter Detail, @@ -838,16 +838,16 @@ From date can not be less than employee's joining date.,Ab dem Datum darf das Be To date can not be greater than employee's relieving date.,Bisher kann das Entlastungsdatum des Mitarbeiters nicht überschritten werden., Payroll date can not be greater than employee's relieving date.,Das Abrechnungsdatum darf nicht größer sein als das Entlastungsdatum des Mitarbeiters., Condition and formula,Zustand und Formel, -Total Allocated Leave(s),Gesamte zugewiesene Urlaubstage, -Expired Leave(s),Verfallene Urlaubstage, -Used Leave(s),Verbrauchte Urlaubstage, -Leave(s) Pending Approval,Urlaubstage zur Genehmigung ausstehend, -Available Leave(s),Verfügbare Urlaubstage, +Total Allocated Leave(s),Gesamte zugewiesene Abwesenheiten, +Expired Leave(s),Verfallene Abwesenheiten, +Used Leave(s),Verbrauchte Abwesenheiten, +Leave(s) Pending Approval,Zu genehmigende Abwesenheiten, +Available Leave(s),Verfügbare Abwesenheiten, Earnings & Deductions,Erträge & Abzüge, -Available Leaves,Verfügbare Urlaubstage, +Available Leaves,Verfügbare Abwesenheiten, IFSC Code,IFSC-Code, Others,Andere, -Total Allocated Leaves,Insgesamt zugeteilte Urlaubstage, -Used Leaves,Genutzte Urlaubstage, +Total Allocated Leaves,Insgesamt zugeteilte Abwesenheiten, +Used Leaves,Genutzte Abwesenheiten, PAN Number,PAN-Nummer, -Expired Leaves,Verfallende Urlaubstage, +Expired Leaves,Verfallende Abwesenheiten, From 6ab422764a0fd940d07a0df6c1ac25bb23664189 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:43:52 +0530 Subject: [PATCH 03/79] fix(Staffing Plan): honour company default currency (backport #1461) (#1468) (cherry picked from commit 994c6dbc426de1bdb5f9a6f111a525fd018dd44d) Co-authored-by: Akash Tom <61287991+krantheman@users.noreply.github.com> --- hrms/hr/doctype/staffing_plan/staffing_plan.json | 3 ++- .../staffing_plan_detail/staffing_plan_detail.json | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hrms/hr/doctype/staffing_plan/staffing_plan.json b/hrms/hr/doctype/staffing_plan/staffing_plan.json index 478e058aa6..7007811f25 100644 --- a/hrms/hr/doctype/staffing_plan/staffing_plan.json +++ b/hrms/hr/doctype/staffing_plan/staffing_plan.json @@ -70,6 +70,7 @@ "fieldname": "total_estimated_budget", "fieldtype": "Currency", "label": "Total Estimated Budget", + "options": "Company:company:default_currency", "read_only": 1 }, { @@ -89,7 +90,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2023-01-11 18:06:32.989641", + "modified": "2024-02-23 18:20:51.305004", "modified_by": "Administrator", "module": "HR", "name": "Staffing Plan", diff --git a/hrms/hr/doctype/staffing_plan_detail/staffing_plan_detail.json b/hrms/hr/doctype/staffing_plan_detail/staffing_plan_detail.json index 77164c4e67..30f3d0de8c 100644 --- a/hrms/hr/doctype/staffing_plan_detail/staffing_plan_detail.json +++ b/hrms/hr/doctype/staffing_plan_detail/staffing_plan_detail.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2018-04-13 18:04:20.978931", "doctype": "DocType", "editable_grid": 1, @@ -33,7 +34,8 @@ "fieldname": "estimated_cost_per_position", "fieldtype": "Currency", "in_list_view": 1, - "label": "Estimated Cost Per Position" + "label": "Estimated Cost Per Position", + "options": "Company:company:default_currency" }, { "fieldname": "column_break_5", @@ -62,11 +64,13 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Total Estimated Cost", + "options": "Company:company:default_currency", "read_only": 1 } ], "istable": 1, - "modified": "2019-06-24 18:40:37.140178", + "links": [], + "modified": "2024-02-23 18:21:52.585104", "modified_by": "Administrator", "module": "HR", "name": "Staffing Plan Detail", @@ -75,5 +79,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 3a40a8b6e7de98414d9c2651df6c17a7b6e226fc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:03:56 +0530 Subject: [PATCH 04/79] fix: enable no copy for Pending Amount & Returned Amount (backport #1466) (#1471) * fix: no copy for Pending Amount & Returned Amount * fix: modified (cherry picked from commit cd6b82a03254a1b11cb16acf0748be01a2f732b7) Co-authored-by: Rehan Ansari <110723484+rehanansari26@users.noreply.github.com> --- hrms/hr/doctype/employee_advance/employee_advance.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hrms/hr/doctype/employee_advance/employee_advance.json b/hrms/hr/doctype/employee_advance/employee_advance.json index 903b4fb5bc..da85339975 100644 --- a/hrms/hr/doctype/employee_advance/employee_advance.json +++ b/hrms/hr/doctype/employee_advance/employee_advance.json @@ -173,6 +173,7 @@ "fieldname": "return_amount", "fieldtype": "Currency", "label": "Returned Amount", + "no_copy": 1, "options": "currency", "read_only": 1 }, @@ -187,6 +188,7 @@ "fieldname": "pending_amount", "fieldtype": "Currency", "label": "Pending Amount", + "no_copy": 1, "options": "currency", "read_only": 1 }, @@ -234,7 +236,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2023-08-19 23:40:06.298367", + "modified": "2024-02-26 14:42:01.853329", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", @@ -303,4 +305,4 @@ ], "title_field": "employee_name", "track_changes": 1 -} \ No newline at end of file +} From b2e6efe874ee0af71903f91baa1130194a87cfa5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:29:13 +0530 Subject: [PATCH 05/79] fix(Payroll Entry): consideration of Salary Slip Based on Timesheet as a filter query when it is disabled (backport #1474) (#1482) (cherry picked from commit 9b80004bfebddcfe7d1ec6dec623622399309ec3) Co-authored-by: Akash Tom <61287991+krantheman@users.noreply.github.com> --- hrms/payroll/doctype/payroll_entry/payroll_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/payroll_entry/payroll_entry.js b/hrms/payroll/doctype/payroll_entry/payroll_entry.js index 575fbab880..6d0137d7a1 100644 --- a/hrms/payroll/doctype/payroll_entry/payroll_entry.js +++ b/hrms/payroll/doctype/payroll_entry/payroll_entry.js @@ -217,7 +217,7 @@ frappe.ui.form.on('Payroll Entry', { 'currency', 'department', 'branch', 'designation', 'salary_slip_based_on_timesheet','grade']; fields.forEach(field => { - if (frm.doc[field]) { + if (frm.doc[field] || frm.doc[field] === 0) { filters[field] = frm.doc[field]; } }); From a9fee08897cdd1c5a6460212bc8e6ea8639e4525 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 21:37:07 +0530 Subject: [PATCH 06/79] refactor: update exchange rate of reference document supplied by reconciliation tool (backport #1489) (#1491) (cherry picked from commit 565f123819317b73c6db5724bf7859f4e47d143c) Co-authored-by: ruthra kumar --- hrms/overrides/employee_payment_entry.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hrms/overrides/employee_payment_entry.py b/hrms/overrides/employee_payment_entry.py index 1b4131a59b..1722bb5df5 100644 --- a/hrms/overrides/employee_payment_entry.py +++ b/hrms/overrides/employee_payment_entry.py @@ -28,7 +28,10 @@ def get_valid_reference_doctypes(self): return ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity") def set_missing_ref_details( - self, force: bool = False, update_ref_details_only_for: list | None = None + self, + force: bool = False, + update_ref_details_only_for: list | None = None, + ref_exchange_rate: float | None = None, ) -> None: for d in self.get("references"): if d.allocated_amount: @@ -41,6 +44,9 @@ def set_missing_ref_details( d.reference_doctype, d.reference_name, self.party_account_currency ) + if ref_exchange_rate: + ref_details.update({"exchange_rate": ref_exchange_rate}) + for field, value in ref_details.items(): if d.exchange_gain_loss: # for cases where gain/loss is booked into invoice From fb41749170c6deed957a7024bcfd18bb3d531b56 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:40:00 +0530 Subject: [PATCH 07/79] test: fix `test_consider_marked_attendance_on_holidays_with_unmarked_attendance` (backport #1494) (#1496) - mark absent on holiday after DOJ only - make sure unmarked attendance is not on a holiday (cherry picked from commit bb22a85001ae8fde6b48fa57cef0a63676abc208) Co-authored-by: Rucha Mahabal --- .../payroll/doctype/salary_slip/test_salary_slip.py | 13 +++++++++---- hrms/tests/test_utils.py | 10 +++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py index cfc7295794..55c3d00fcb 100644 --- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py @@ -533,21 +533,26 @@ def test_consider_marked_attendance_on_holidays_with_unmarked_attendance(self): joining_date = add_days(month_start_date, 3) emp_id = make_employee( - "test_salary_slip_with_holidays_included@salary.com", + "test_salary_slip_with_holidays_included1@salary.com", status="Active", - joining_date=joining_date, + date_of_joining=joining_date, relieving_date=None, ) - for days in range(date_diff(month_end_date, add_days(joining_date, 1)) + 1): + for days in range(date_diff(month_end_date, joining_date) + 1): date = add_days(joining_date, days) if not is_holiday("Salary Slip Test Holiday List", date): mark_attendance(emp_id, date, "Present", ignore_validate=True) # mark absent on holiday - first_sunday = get_first_sunday(for_date=getdate()) + first_sunday = get_first_sunday(for_date=joining_date, find_after_for_date=True) mark_attendance(emp_id, first_sunday, "Absent", ignore_validate=True) + # unmarked attendance for a day + frappe.db.delete( + "Attendance", {"employee": emp_id, "attendance_date": add_days(first_sunday, 1)} + ) + ss = make_employee_salary_slip( emp_id, "Monthly", diff --git a/hrms/tests/test_utils.py b/hrms/tests/test_utils.py index 3dd63b17d1..33ab8615ea 100644 --- a/hrms/tests/test_utils.py +++ b/hrms/tests/test_utils.py @@ -46,9 +46,17 @@ def set_defaults(): ) -def get_first_sunday(holiday_list="Salary Slip Test Holiday List", for_date=None): +def get_first_sunday( + holiday_list="Salary Slip Test Holiday List", for_date=None, find_after_for_date=False +): date = for_date or getdate() month_start_date = get_first_day(date) + + if find_after_for_date: + # explictly find first sunday after for_date + # useful when DOJ is after the month start + month_start_date = date + month_end_date = get_last_day(date) first_sunday = frappe.db.sql( """ From f220b7b6f8cc25e128734d8f843bb30c494988d2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 27 Jan 2024 11:30:17 +0530 Subject: [PATCH 08/79] feat: add push notification client (cherry picked from commit 054c25e3681a5274c7390eb777624d4421e2efe9) --- .../public/frappe-push-notification-sw.js | 39 +++ frontend/public/frappe-push-notification.js | 273 ++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 frontend/public/frappe-push-notification-sw.js create mode 100644 frontend/public/frappe-push-notification.js diff --git a/frontend/public/frappe-push-notification-sw.js b/frontend/public/frappe-push-notification-sw.js new file mode 100644 index 0000000000..ed0cf9b682 --- /dev/null +++ b/frontend/public/frappe-push-notification-sw.js @@ -0,0 +1,39 @@ +import {initializeApp} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js"; +import {getMessaging, onBackgroundMessage} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-sw.js"; + +const jsonConfig = new URL(location).searchParams.get('config'); +const firebaseApp = initializeApp(JSON.parse(jsonConfig)); +const messaging = getMessaging(firebaseApp); +function isChrome() { + return navigator.userAgent.toLowerCase().includes("chrome") +} +onBackgroundMessage(messaging, (payload) => { + const notificationTitle = payload.data.title; + let notificationOptions = { + body: payload.data.body || '' + }; + if (isChrome()) { + notificationOptions['data'] = { + url: payload.data.click_action + } + } else { + if(payload.data.click_action){ + notificationOptions['actions'] = [{ + action: payload.data.click_action, + title: 'View details' + }] + } + } + self.registration.showNotification(notificationTitle, notificationOptions); +}); + + +if(isChrome()) { + self.addEventListener('notificationclick', (event) => { + event.stopImmediatePropagation(); + event.notification.close(); + if (event.notification.data && event.notification.data.url) { + clients.openWindow(event.notification.data.url) + } + }) +} \ No newline at end of file diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js new file mode 100644 index 0000000000..63d8e5a12c --- /dev/null +++ b/frontend/public/frappe-push-notification.js @@ -0,0 +1,273 @@ +import {initializeApp} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js"; +import { + getMessaging, + getToken, + onMessage as onFCMMessage, + isSupported, + deleteToken +} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging.js"; + + +class FrappePushNotification { + static relayServerBaseURL = 'http://notification-relay:8003'; + + // Type definitions + /** + * Web Config + * FCM web config to initialize firebase app + * + * @typedef {object} webConfigType + * @property {string} projectId + * @property {string} appId + * @property {string} apiKey + * @property {string} authDomain + * @property {string} messagingSenderId + */ + + /** + * Constructor + * + * @param {string} projectName + */ + constructor(projectName) { + // client info + this.projectName = projectName; + /** @type {webConfigType | null} */ + this.webConfig = null; + this.vapidPublicKey = ""; + this.token = null; + + // state + this.initialized = false; + this.messaging = null; + /** @type {ServiceWorkerRegistration | null} */ + this.serviceWorkerRegistration = null; + + // event handlers + this.onMessageHandler = null; + } + + /** + * Initialize notification service client + * + * @param {ServiceWorkerRegistration} serviceWorkerRegistration - Service worker registration object + * @returns {Promise} + */ + async initialize(serviceWorkerRegistration) { + if (this.initialized) { + return; + } + this.serviceWorkerRegistration = serviceWorkerRegistration; + const config = await this.fetchWebConfig(); + this.messaging = getMessaging(initializeApp(config)); + this.onMessage(this.onMessageHandler); + this.initialized = true; + } + + /** + * Append config to service worker URL + * + * @param {string} url - Service worker URL + * @param {string} parameter_name - Parameter name to add config + * @returns {Promise} - Service worker URL with config + */ + async appendConfigToServiceWorkerURL(url, parameter_name = "config") { + let config = await this.fetchWebConfig(); + const encode_config = encodeURIComponent(JSON.stringify(config)); + return `${url}?${parameter_name}=${encode_config}`; + } + + /** + * Fetch web config of the project + * + * @returns {Promise} + */ + async fetchWebConfig() { + if (this.webConfig !== null && this.webConfig !== undefined) { + return this.webConfig; + } + let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` + let response = await fetch(url); + let response_json = await response.json(); + this.webConfig = response_json.config; + return this.webConfig; + } + + + /** + * Fetch VAPID public key + * + * @returns {Promise} + */ + async fetchVapidPublicKey() { + if (this.vapidPublicKey !== "") { + return this.vapidPublicKey; + } + let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` + let response = await fetch(url); + let response_json = await response.json(); + this.vapidPublicKey = response_json.vapid_public_key; + return this.vapidPublicKey; + } + + + /** + * Register on message handler + * + * @param {function( + * { + * data:{ + * title: string, + * body: string, + * click_action: string|null, + * } + * } + * )} callback - Callback function to handle message + */ + onMessage(callback) { + if (callback == null) return; + this.onMessageHandler = callback; + if (this.messaging == null) return; + onFCMMessage(this.messaging, this.onMessageHandler) + } + + /** + * Check if notification is enabled + * + * @returns {boolean} + */ + isNotificationEnabled() { + return localStorage.getItem(`firebase_token_${this.projectName}`) !== null; + } + + /** + * Enable notification + * This will return notification permission status and token + * + * @returns {Promise<{permission_granted: boolean, token: string}>} + */ + async enableNotification() { + if (!await isSupported()) { + throw new Error("Push notification not supported"); + } + // Return if token already presence in the instance + if (this.token != null) { + return { + permission_granted: true, + token: this.token, + }; + } + // ask for permission + const permission = await Notification.requestPermission(); + if (permission !== "granted") { + return { + permission_granted: false, + token: "", + }; + } + // check in local storage for old token + let oldToken = localStorage.getItem(`firebase_token_${this.projectName}`); + const vapidKey = await this.fetchVapidPublicKey(); + let newToken = await getToken(this.messaging, { + vapidKey: vapidKey, + serviceWorkerRegistration: this.serviceWorkerRegistration, + }) + // register new token if token is changed + if (oldToken !== newToken) { + // unsubscribe old token + if (oldToken) { + await this.unregisterTokenHandler(oldToken); + } + // subscribe push notification and register token + let isSubscriptionSuccessful = await this.registerTokenHandler(newToken); + if (isSubscriptionSuccessful === false) { + throw new Error("Failed to subscribe to push notification"); + } + // save token to local storage + localStorage.setItem(`firebase_token_${this.projectName}`, newToken); + } + this.token = newToken; + return { + permission_granted: true, + token: newToken + }; + } + + /** + * Disable notification + * This will delete token from firebase and unsubscribe from push notification + * + * @returns {Promise} + */ + async disableNotification() { + if (this.token == null) { + // try to fetch token from local storage + this.token = localStorage.getItem(`firebase_token_${this.projectName}`); + if (this.token == null || this.token === "") { + return; + } + } + // delete old token from firebase + try { + await deleteToken(this.messaging) + } catch (e) { + console.error("Failed to delete token from firebase"); + console.error(e); + } + try { + await this.unregisterTokenHandler(this.token); + } catch { + console.error("Failed to unsubscribe from push notification"); + console.error(e); + } + // remove token + localStorage.removeItem(`firebase_token_${this.projectName}`); + this.token = null; + } + + /** + * Register Token Handler + * + * @param {string} token - FCM token returned by {@link enableNotification} method + * @returns {promise} + */ + async registerTokenHandler(token) { + try { + let response = await fetch("/api/method/frappe.push_notification.subscribe?fcm_token=" + token + "&project_name=" + this.projectName, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + } + }); + return response.status === 200; + } catch (e) { + console.error(e) + return false + } + } + + /** + * Unregister Token Handler + * + * @param {string} token - FCM token returned by `enableNotification` method + * @returns {promise} + */ + async unregisterTokenHandler(token) { + try { + let response = await fetch("/api/method/frappe.push_notification.unsubscribe?fcm_token=" + token + "&project_name=" + this.projectName, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + } + }); + return response.status === 200; + + } catch (e) { + console.error(e) + return false + } + } + +} + +export default FrappePushNotification \ No newline at end of file From 247c102757b8e0c0d9a3e5a5f906ab22070b526a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 27 Jan 2024 12:57:03 +0530 Subject: [PATCH 09/79] feat: add App Settings to enable push notifications (cherry picked from commit 7e5f2980a2600fc0f9600b0342c3f5302d49a667) --- frontend/index.html | 24 ++++++++ frontend/src/router/index.js | 5 ++ frontend/src/views/AppSettings.vue | 90 ++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 frontend/src/views/AppSettings.vue diff --git a/frontend/index.html b/frontend/index.html index 30fa865ee7..25015e039f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -176,6 +176,30 @@ href="/assets/hrms/manifest/apple-splash-1136-640.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" /> + + + +
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 623c6a0c58..01b7cc20d3 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -56,6 +56,11 @@ const routes = [ name: "Notifications", component: () => import("@/views/Notifications.vue"), }, + { + path: "/settings", + name: "Settings", + component: () => import("@/views/AppSettings.vue"), + }, { path: "/invalid-employee", name: "InvalidEmployee", diff --git a/frontend/src/views/AppSettings.vue b/frontend/src/views/AppSettings.vue new file mode 100644 index 0000000000..973433ad08 --- /dev/null +++ b/frontend/src/views/AppSettings.vue @@ -0,0 +1,90 @@ + + + + From 89af3991c08305585d87b143519c3e07fcdbc96d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 4 Feb 2024 23:04:43 +0530 Subject: [PATCH 10/79] feat: send push notification on PWA Notification log insertion (cherry picked from commit 876a29fa51aa7cb8c3d9b4ef08d37d825c0bd465) --- .../pwa_notification/pwa_notification.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/hrms/hr/doctype/pwa_notification/pwa_notification.py b/hrms/hr/doctype/pwa_notification/pwa_notification.py index 190dc71760..cd5792425f 100644 --- a/hrms/hr/doctype/pwa_notification/pwa_notification.py +++ b/hrms/hr/doctype/pwa_notification/pwa_notification.py @@ -1,6 +1,8 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import frappe from frappe.model.document import Document +from frappe.push_notification import PushNotification import hrms @@ -8,3 +10,18 @@ class PWANotification(Document): def on_update(self): hrms.refetch_resource("hrms:notifications", self.to_user) + + def after_insert(self): + self.send_push_notification() + + def send_push_notification(self): + try: + url = frappe.utils.get_url() + + push_notification = PushNotification("hrms") + if push_notification.is_enabled(): + push_notification.send_notification_to_user( + self.to_user, self.reference_document_type, self.message, link=url, truncate_body=True + ) + except Exception: + self.log_error(f"Error sending push notification: {self.name}") From f677ce35f28e18b0dbdb16a1ef35e740da5cd3de Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 21 Feb 2024 23:57:21 +0530 Subject: [PATCH 11/79] feat: show in-app notification popup (cherry picked from commit f007934b67e8a2be3a4803abc2ef8912762449f4) --- frontend/src/App.vue | 2 ++ .../src/components/FrappeNotification.vue | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 frontend/src/components/FrappeNotification.vue diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 929fd19d01..2cc9531537 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,6 +4,7 @@ + @@ -13,4 +14,5 @@ import { IonApp, IonRouterOutlet } from "@ionic/vue" import { Toasts } from "frappe-ui" import InstallPrompt from "@/components/InstallPrompt.vue" +import FrappeNotification from "@/components/FrappeNotification.vue" diff --git a/frontend/src/components/FrappeNotification.vue b/frontend/src/components/FrappeNotification.vue new file mode 100644 index 0000000000..7254a495c3 --- /dev/null +++ b/frontend/src/components/FrappeNotification.vue @@ -0,0 +1,28 @@ + + + From 375054a9f13cc6112921f43b2dea0d872e81a95d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 23 Feb 2024 13:33:06 +0530 Subject: [PATCH 12/79] chore: auto-format code (cherry picked from commit e6ddb51b94bfe8939182422eb47bf73fe615da34) --- frontend/index.html | 38 +++-- .../public/frappe-push-notification-sw.js | 50 +++--- frontend/public/frappe-push-notification.js | 160 +++++++++--------- .../src/components/FrappeNotification.vue | 1 + frontend/src/components/ListView.vue | 10 +- frontend/src/views/AppSettings.vue | 76 +++++---- 6 files changed, 177 insertions(+), 158 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 25015e039f..201f731932 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -177,27 +177,33 @@ media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" /> - - diff --git a/frontend/public/frappe-push-notification-sw.js b/frontend/public/frappe-push-notification-sw.js index ed0cf9b682..6361a5dc69 100644 --- a/frontend/public/frappe-push-notification-sw.js +++ b/frontend/public/frappe-push-notification-sw.js @@ -1,39 +1,43 @@ -import {initializeApp} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js"; -import {getMessaging, onBackgroundMessage} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-sw.js"; +import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js" +import { + getMessaging, + onBackgroundMessage, +} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-sw.js" -const jsonConfig = new URL(location).searchParams.get('config'); -const firebaseApp = initializeApp(JSON.parse(jsonConfig)); -const messaging = getMessaging(firebaseApp); +const jsonConfig = new URL(location).searchParams.get("config") +const firebaseApp = initializeApp(JSON.parse(jsonConfig)) +const messaging = getMessaging(firebaseApp) function isChrome() { return navigator.userAgent.toLowerCase().includes("chrome") } onBackgroundMessage(messaging, (payload) => { - const notificationTitle = payload.data.title; + const notificationTitle = payload.data.title let notificationOptions = { - body: payload.data.body || '' - }; + body: payload.data.body || "", + } if (isChrome()) { - notificationOptions['data'] = { - url: payload.data.click_action + notificationOptions["data"] = { + url: payload.data.click_action, } } else { - if(payload.data.click_action){ - notificationOptions['actions'] = [{ - action: payload.data.click_action, - title: 'View details' - }] + if (payload.data.click_action) { + notificationOptions["actions"] = [ + { + action: payload.data.click_action, + title: "View details", + }, + ] } } - self.registration.showNotification(notificationTitle, notificationOptions); -}); - + self.registration.showNotification(notificationTitle, notificationOptions) +}) -if(isChrome()) { - self.addEventListener('notificationclick', (event) => { - event.stopImmediatePropagation(); - event.notification.close(); +if (isChrome()) { + self.addEventListener("notificationclick", (event) => { + event.stopImmediatePropagation() + event.notification.close() if (event.notification.data && event.notification.data.url) { clients.openWindow(event.notification.data.url) } }) -} \ No newline at end of file +} diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 63d8e5a12c..35fabd45eb 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -1,15 +1,14 @@ -import {initializeApp} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js"; +import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js" import { getMessaging, getToken, onMessage as onFCMMessage, isSupported, - deleteToken -} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging.js"; - + deleteToken, +} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging.js" class FrappePushNotification { - static relayServerBaseURL = 'http://notification-relay:8003'; + static relayServerBaseURL = "http://notification-relay:8003" // Type definitions /** @@ -31,20 +30,20 @@ class FrappePushNotification { */ constructor(projectName) { // client info - this.projectName = projectName; + this.projectName = projectName /** @type {webConfigType | null} */ - this.webConfig = null; - this.vapidPublicKey = ""; - this.token = null; + this.webConfig = null + this.vapidPublicKey = "" + this.token = null // state - this.initialized = false; - this.messaging = null; + this.initialized = false + this.messaging = null /** @type {ServiceWorkerRegistration | null} */ - this.serviceWorkerRegistration = null; + this.serviceWorkerRegistration = null // event handlers - this.onMessageHandler = null; + this.onMessageHandler = null } /** @@ -55,13 +54,13 @@ class FrappePushNotification { */ async initialize(serviceWorkerRegistration) { if (this.initialized) { - return; + return } - this.serviceWorkerRegistration = serviceWorkerRegistration; - const config = await this.fetchWebConfig(); - this.messaging = getMessaging(initializeApp(config)); - this.onMessage(this.onMessageHandler); - this.initialized = true; + this.serviceWorkerRegistration = serviceWorkerRegistration + const config = await this.fetchWebConfig() + this.messaging = getMessaging(initializeApp(config)) + // this.onMessage(this.onMessageHandler) + this.initialized = true } /** @@ -72,9 +71,9 @@ class FrappePushNotification { * @returns {Promise} - Service worker URL with config */ async appendConfigToServiceWorkerURL(url, parameter_name = "config") { - let config = await this.fetchWebConfig(); - const encode_config = encodeURIComponent(JSON.stringify(config)); - return `${url}?${parameter_name}=${encode_config}`; + let config = await this.fetchWebConfig() + const encode_config = encodeURIComponent(JSON.stringify(config)) + return `${url}?${parameter_name}=${encode_config}` } /** @@ -84,16 +83,15 @@ class FrappePushNotification { */ async fetchWebConfig() { if (this.webConfig !== null && this.webConfig !== undefined) { - return this.webConfig; + return this.webConfig } let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` - let response = await fetch(url); - let response_json = await response.json(); - this.webConfig = response_json.config; - return this.webConfig; + let response = await fetch(url) + let response_json = await response.json() + this.webConfig = response_json.config + return this.webConfig } - /** * Fetch VAPID public key * @@ -101,16 +99,15 @@ class FrappePushNotification { */ async fetchVapidPublicKey() { if (this.vapidPublicKey !== "") { - return this.vapidPublicKey; + return this.vapidPublicKey } let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` - let response = await fetch(url); - let response_json = await response.json(); - this.vapidPublicKey = response_json.vapid_public_key; - return this.vapidPublicKey; + let response = await fetch(url) + let response_json = await response.json() + this.vapidPublicKey = response_json.vapid_public_key + return this.vapidPublicKey } - /** * Register on message handler * @@ -125,9 +122,10 @@ class FrappePushNotification { * )} callback - Callback function to handle message */ onMessage(callback) { - if (callback == null) return; - this.onMessageHandler = callback; - if (this.messaging == null) return; + console.log("onMessage") + if (callback == null) return + this.onMessageHandler = callback + if (this.messaging == null) return onFCMMessage(this.messaging, this.onMessageHandler) } @@ -137,7 +135,7 @@ class FrappePushNotification { * @returns {boolean} */ isNotificationEnabled() { - return localStorage.getItem(`firebase_token_${this.projectName}`) !== null; + return localStorage.getItem(`firebase_token_${this.projectName}`) !== null } /** @@ -147,27 +145,27 @@ class FrappePushNotification { * @returns {Promise<{permission_granted: boolean, token: string}>} */ async enableNotification() { - if (!await isSupported()) { - throw new Error("Push notification not supported"); + if (!(await isSupported())) { + throw new Error("Push notification not supported") } // Return if token already presence in the instance if (this.token != null) { return { permission_granted: true, token: this.token, - }; + } } // ask for permission - const permission = await Notification.requestPermission(); + const permission = await Notification.requestPermission() if (permission !== "granted") { return { permission_granted: false, token: "", - }; + } } // check in local storage for old token - let oldToken = localStorage.getItem(`firebase_token_${this.projectName}`); - const vapidKey = await this.fetchVapidPublicKey(); + let oldToken = localStorage.getItem(`firebase_token_${this.projectName}`) + const vapidKey = await this.fetchVapidPublicKey() let newToken = await getToken(this.messaging, { vapidKey: vapidKey, serviceWorkerRegistration: this.serviceWorkerRegistration, @@ -176,21 +174,21 @@ class FrappePushNotification { if (oldToken !== newToken) { // unsubscribe old token if (oldToken) { - await this.unregisterTokenHandler(oldToken); + await this.unregisterTokenHandler(oldToken) } // subscribe push notification and register token - let isSubscriptionSuccessful = await this.registerTokenHandler(newToken); + let isSubscriptionSuccessful = await this.registerTokenHandler(newToken) if (isSubscriptionSuccessful === false) { - throw new Error("Failed to subscribe to push notification"); + throw new Error("Failed to subscribe to push notification") } // save token to local storage - localStorage.setItem(`firebase_token_${this.projectName}`, newToken); + localStorage.setItem(`firebase_token_${this.projectName}`, newToken) } - this.token = newToken; + this.token = newToken return { permission_granted: true, - token: newToken - }; + token: newToken, + } } /** @@ -202,27 +200,27 @@ class FrappePushNotification { async disableNotification() { if (this.token == null) { // try to fetch token from local storage - this.token = localStorage.getItem(`firebase_token_${this.projectName}`); + this.token = localStorage.getItem(`firebase_token_${this.projectName}`) if (this.token == null || this.token === "") { - return; + return } } // delete old token from firebase try { await deleteToken(this.messaging) } catch (e) { - console.error("Failed to delete token from firebase"); - console.error(e); + console.error("Failed to delete token from firebase") + console.error(e) } try { - await this.unregisterTokenHandler(this.token); + await this.unregisterTokenHandler(this.token) } catch { - console.error("Failed to unsubscribe from push notification"); - console.error(e); + console.error("Failed to unsubscribe from push notification") + console.error(e) } // remove token - localStorage.removeItem(`firebase_token_${this.projectName}`); - this.token = null; + localStorage.removeItem(`firebase_token_${this.projectName}`) + this.token = null } /** @@ -233,13 +231,19 @@ class FrappePushNotification { */ async registerTokenHandler(token) { try { - let response = await fetch("/api/method/frappe.push_notification.subscribe?fcm_token=" + token + "&project_name=" + this.projectName, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', + let response = await fetch( + "/api/method/frappe.push_notification.subscribe?fcm_token=" + + token + + "&project_name=" + + this.projectName, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, } - }); - return response.status === 200; + ) + return response.status === 200 } catch (e) { console.error(e) return false @@ -254,20 +258,24 @@ class FrappePushNotification { */ async unregisterTokenHandler(token) { try { - let response = await fetch("/api/method/frappe.push_notification.unsubscribe?fcm_token=" + token + "&project_name=" + this.projectName, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', + let response = await fetch( + "/api/method/frappe.push_notification.unsubscribe?fcm_token=" + + token + + "&project_name=" + + this.projectName, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, } - }); - return response.status === 200; - + ) + return response.status === 200 } catch (e) { console.error(e) return false } } - } -export default FrappePushNotification \ No newline at end of file +export default FrappePushNotification diff --git a/frontend/src/components/FrappeNotification.vue b/frontend/src/components/FrappeNotification.vue index 7254a495c3..8d0835ab57 100644 --- a/frontend/src/components/FrappeNotification.vue +++ b/frontend/src/components/FrappeNotification.vue @@ -20,6 +20,7 @@ const notificationBody = ref("") onMounted(() => { window.frappePushNotification.onMessage((payload) => { + console.log("Push Notification", payload) notificationTitle.value = payload.data.title notificationBody.value = payload.data.body showDialog.value = true diff --git a/frontend/src/components/ListView.vue b/frontend/src/components/ListView.vue index 4e3cf1ee35..f6bf3c38d6 100644 --- a/frontend/src/components/ListView.vue +++ b/frontend/src/components/ListView.vue @@ -109,15 +109,7 @@ - From 1b4a383e21e49632bf3e414d7414feb25e29ed56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Feb 2024 17:15:44 +0530 Subject: [PATCH 13/79] chore: add workbox dependencies (cherry picked from commit a4d7c7749e64a0bdbdb933d2f46fb06b578383ce) # Conflicts: # frontend/package.json --- frontend/package.json | 8 + frontend/yarn.lock | 427 ++++++++++++++++++++++++++---------------- 2 files changed, 272 insertions(+), 163 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 46e1842693..c62e02a0f9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,14 @@ "eslint": "^8.39.0", "eslint-plugin-vue": "^9.11.0", "prettier": "^2.8.8", +<<<<<<< HEAD "tailwindcss": "^3.0.15" +======= + "tailwindcss": "^3.0.15", + "vite": "^5.1.4", + "vite-plugin-pwa": "^0.19.0", + "workbox-precaching": "^7.0.0", + "workbox-core": "^7.0.0" +>>>>>>> a4d7c7749 (chore: add workbox dependencies) } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 576ccd2c98..9f71a9b5d1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -926,115 +926,120 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@esbuild/android-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" - integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== - -"@esbuild/android-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" - integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== - -"@esbuild/android-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" - integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== - -"@esbuild/darwin-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" - integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== - -"@esbuild/darwin-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" - integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== - -"@esbuild/freebsd-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" - integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== - -"@esbuild/freebsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" - integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== - -"@esbuild/linux-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" - integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== - -"@esbuild/linux-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" - integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== - -"@esbuild/linux-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" - integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== - -"@esbuild/linux-loong64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" - integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== - -"@esbuild/linux-mips64el@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" - integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== - -"@esbuild/linux-ppc64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" - integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== - -"@esbuild/linux-riscv64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" - integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== - -"@esbuild/linux-s390x@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" - integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== - -"@esbuild/linux-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" - integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== - -"@esbuild/netbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" - integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== - -"@esbuild/openbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" - integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== - -"@esbuild/sunos-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" - integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== - -"@esbuild/win32-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" - integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== - -"@esbuild/win32-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" - integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== - -"@esbuild/win32-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" - integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== +"@esbuild/aix-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" + integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA== + +"@esbuild/android-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4" + integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA== + +"@esbuild/android-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824" + integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w== + +"@esbuild/android-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d" + integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew== + +"@esbuild/darwin-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e" + integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== + +"@esbuild/darwin-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd" + integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A== + +"@esbuild/freebsd-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487" + integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA== + +"@esbuild/freebsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c" + integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg== + +"@esbuild/linux-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b" + integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA== + +"@esbuild/linux-arm@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef" + integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w== + +"@esbuild/linux-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601" + integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA== + +"@esbuild/linux-loong64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299" + integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA== + +"@esbuild/linux-mips64el@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec" + integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w== + +"@esbuild/linux-ppc64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8" + integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg== + +"@esbuild/linux-riscv64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf" + integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg== + +"@esbuild/linux-s390x@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8" + integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg== + +"@esbuild/linux-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78" + integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg== + +"@esbuild/netbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b" + integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA== + +"@esbuild/openbsd-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0" + integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw== + +"@esbuild/sunos-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30" + integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA== + +"@esbuild/win32-arm64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae" + integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A== + +"@esbuild/win32-ia32@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67" + integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ== + +"@esbuild/win32-x64@0.19.12": + version "0.19.12" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae" + integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.3.0": version "4.4.0" @@ -1299,6 +1304,71 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/rollup-android-arm-eabi@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz#38c3abd1955a3c21d492af6b1a1dca4bb1d894d6" + integrity sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w== + +"@rollup/rollup-android-arm64@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz#3822e929f415627609e53b11cec9a4be806de0e2" + integrity sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ== + +"@rollup/rollup-darwin-arm64@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz#6c082de71f481f57df6cfa3701ab2a7afde96f69" + integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ== + +"@rollup/rollup-darwin-x64@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz#c34ca0d31f3c46a22c9afa0e944403eea0edcfd8" + integrity sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg== + +"@rollup/rollup-linux-arm-gnueabihf@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz#48e899c1e438629c072889b824a98787a7c2362d" + integrity sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA== + +"@rollup/rollup-linux-arm64-gnu@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz#788c2698a119dc229062d40da6ada8a090a73a68" + integrity sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA== + +"@rollup/rollup-linux-arm64-musl@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz#3882a4e3a564af9e55804beeb67076857b035ab7" + integrity sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ== + +"@rollup/rollup-linux-riscv64-gnu@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz#0c6ad792e1195c12bfae634425a3d2aa0fe93ab7" + integrity sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw== + +"@rollup/rollup-linux-x64-gnu@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz#9d62485ea0f18d8674033b57aa14fb758f6ec6e3" + integrity sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA== + +"@rollup/rollup-linux-x64-musl@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz#50e8167e28b33c977c1f813def2b2074d1435e05" + integrity sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw== + +"@rollup/rollup-win32-arm64-msvc@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz#68d233272a2004429124494121a42c4aebdc5b8e" + integrity sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw== + +"@rollup/rollup-win32-ia32-msvc@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz#366ca62221d1689e3b55a03f4ae12ae9ba595d40" + integrity sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA== + +"@rollup/rollup-win32-x64-msvc@4.12.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz#9ffdf9ed133a7464f4ae187eb9e1294413fab235" + integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg== + "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" @@ -1584,6 +1654,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/node@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.1.tgz#de559d4b33be9a808fd43372ccee822c70f39704" @@ -2247,33 +2322,34 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.18.10: - version "0.18.20" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" - integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== +esbuild@^0.19.3: + version "0.19.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04" + integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg== optionalDependencies: - "@esbuild/android-arm" "0.18.20" - "@esbuild/android-arm64" "0.18.20" - "@esbuild/android-x64" "0.18.20" - "@esbuild/darwin-arm64" "0.18.20" - "@esbuild/darwin-x64" "0.18.20" - "@esbuild/freebsd-arm64" "0.18.20" - "@esbuild/freebsd-x64" "0.18.20" - "@esbuild/linux-arm" "0.18.20" - "@esbuild/linux-arm64" "0.18.20" - "@esbuild/linux-ia32" "0.18.20" - "@esbuild/linux-loong64" "0.18.20" - "@esbuild/linux-mips64el" "0.18.20" - "@esbuild/linux-ppc64" "0.18.20" - "@esbuild/linux-riscv64" "0.18.20" - "@esbuild/linux-s390x" "0.18.20" - "@esbuild/linux-x64" "0.18.20" - "@esbuild/netbsd-x64" "0.18.20" - "@esbuild/openbsd-x64" "0.18.20" - "@esbuild/sunos-x64" "0.18.20" - "@esbuild/win32-arm64" "0.18.20" - "@esbuild/win32-ia32" "0.18.20" - "@esbuild/win32-x64" "0.18.20" + "@esbuild/aix-ppc64" "0.19.12" + "@esbuild/android-arm" "0.19.12" + "@esbuild/android-arm64" "0.19.12" + "@esbuild/android-x64" "0.19.12" + "@esbuild/darwin-arm64" "0.19.12" + "@esbuild/darwin-x64" "0.19.12" + "@esbuild/freebsd-arm64" "0.19.12" + "@esbuild/freebsd-x64" "0.19.12" + "@esbuild/linux-arm" "0.19.12" + "@esbuild/linux-arm64" "0.19.12" + "@esbuild/linux-ia32" "0.19.12" + "@esbuild/linux-loong64" "0.19.12" + "@esbuild/linux-mips64el" "0.19.12" + "@esbuild/linux-ppc64" "0.19.12" + "@esbuild/linux-riscv64" "0.19.12" + "@esbuild/linux-s390x" "0.19.12" + "@esbuild/linux-x64" "0.19.12" + "@esbuild/netbsd-x64" "0.19.12" + "@esbuild/openbsd-x64" "0.19.12" + "@esbuild/sunos-x64" "0.19.12" + "@esbuild/win32-arm64" "0.19.12" + "@esbuild/win32-ia32" "0.19.12" + "@esbuild/win32-x64" "0.19.12" escalade@^3.1.1: version "3.1.1" @@ -2420,10 +2496,10 @@ fast-glob@^3.2.12: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -2563,6 +2639,11 @@ fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3225,6 +3306,11 @@ nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3442,12 +3528,12 @@ postcss@^8.1.10, postcss@^8.4.23, postcss@^8.4.5: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.27: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== +postcss@^8.4.35: + version "8.4.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7" + integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA== dependencies: - nanoid "^3.3.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -3757,11 +3843,26 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" -rollup@^3.27.1: - version "3.29.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" - integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== +rollup@^4.2.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.12.0.tgz#0b6d1e5f3d46bbcf244deec41a7421dc54cc45b5" + integrity sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q== + dependencies: + "@types/estree" "1.0.5" optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.12.0" + "@rollup/rollup-android-arm64" "4.12.0" + "@rollup/rollup-darwin-arm64" "4.12.0" + "@rollup/rollup-darwin-x64" "4.12.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.12.0" + "@rollup/rollup-linux-arm64-gnu" "4.12.0" + "@rollup/rollup-linux-arm64-musl" "4.12.0" + "@rollup/rollup-linux-riscv64-gnu" "4.12.0" + "@rollup/rollup-linux-x64-gnu" "4.12.0" + "@rollup/rollup-linux-x64-musl" "4.12.0" + "@rollup/rollup-win32-arm64-msvc" "4.12.0" + "@rollup/rollup-win32-ia32-msvc" "4.12.0" + "@rollup/rollup-win32-x64-msvc" "4.12.0" fsevents "~2.3.2" rope-sequence@^1.3.0: @@ -4204,27 +4305,27 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vite-plugin-pwa@^0.16.6: - version "0.16.6" - resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.16.6.tgz#71c56d36a8214cdd1539baf907a50ace7bee63d9" - integrity sha512-bQPDOWvhPMwydMoWqohXvIzvrq4X8iuCF+q95qEiaM4yC0ybViGKWMnWcpWp0vcnoLk7QvxHDlK65KUZvqB3Sg== +vite-plugin-pwa@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.19.0.tgz#2223d6014b9b980deb89a7e8aefebafe71d8a7ef" + integrity sha512-Unfb4Jk/ka4HELtpMLIPCmGcW4LFT+CL7Ri1/Of1544CVKXS2ftP91kUkNzkzeI1sGpOdVGuxprVLB9NjMoCAA== dependencies: debug "^4.3.4" - fast-glob "^3.3.1" + fast-glob "^3.3.2" pretty-bytes "^6.1.1" workbox-build "^7.0.0" workbox-window "^7.0.0" -vite@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26" - integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== +vite@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.1.4.tgz#14e9d3e7a6e488f36284ef13cebe149f060bcfb6" + integrity sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg== dependencies: - esbuild "^0.18.10" - postcss "^8.4.27" - rollup "^3.27.1" + esbuild "^0.19.3" + postcss "^8.4.35" + rollup "^4.2.0" optionalDependencies: - fsevents "~2.3.2" + fsevents "~2.3.3" vue-demi@>=0.14.6: version "0.14.6" @@ -4381,7 +4482,7 @@ workbox-cacheable-response@7.0.0: dependencies: workbox-core "7.0.0" -workbox-core@7.0.0: +workbox-core@7.0.0, workbox-core@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545" integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ== @@ -4411,7 +4512,7 @@ workbox-navigation-preload@7.0.0: dependencies: workbox-core "7.0.0" -workbox-precaching@7.0.0: +workbox-precaching@7.0.0, workbox-precaching@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03" integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA== From 572af886017eb43870a3cc5256b890f74d6c70b4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Feb 2024 17:26:09 +0530 Subject: [PATCH 14/79] feat: register custom service worker with firebase push notification initialization - use injectManifest to customize service worker - use injectRegister to control when it gets registered (after fetching firebase config) (cherry picked from commit df1214e9eafe40ce6a39c1937ba3a29e5e3e54b2) --- frontend/index.html | 9 ++++----- .../{frappe-push-notification-sw.js => sw.js} | 16 +++++++++++++++- frontend/vite.config.js | 5 +++++ 3 files changed, 24 insertions(+), 6 deletions(-) rename frontend/public/{frappe-push-notification-sw.js => sw.js} (79%) diff --git a/frontend/index.html b/frontend/index.html index 201f731932..fc8958a1d2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -184,9 +184,7 @@ window.frappePushNotification = new FrappePushNotification("hrms") document.addEventListener("DOMContentLoaded", () => { window.frappePushNotification - .appendConfigToServiceWorkerURL( - "/assets/hrms/frontend/frappe-push-notification-sw.js" - ) + .appendConfigToServiceWorkerURL("/assets/hrms/frontend/sw.js") .then((url) => { navigator.serviceWorker .register(url, { @@ -196,7 +194,7 @@ window.frappePushNotification .initialize(registration) .then(() => { - console.log("Frappe Notification Initialized") + console.log("Frappe Push Notification initialized") }) }) }) @@ -205,7 +203,7 @@ }) }) - +
@@ -215,6 +213,7 @@ + diff --git a/frontend/public/frappe-push-notification-sw.js b/frontend/public/sw.js similarity index 79% rename from frontend/public/frappe-push-notification-sw.js rename to frontend/public/sw.js index 6361a5dc69..cfdad387cd 100644 --- a/frontend/public/frappe-push-notification-sw.js +++ b/frontend/public/sw.js @@ -1,15 +1,26 @@ +import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching" +import { clientsClaim } from "workbox-core" + import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js" import { getMessaging, onBackgroundMessage, } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-sw.js" +// Use the precache manifest generated by Vite +precacheAndRoute(self.__WB_MANIFEST) + +// Clean up old caches +cleanupOutdatedCaches() + const jsonConfig = new URL(location).searchParams.get("config") const firebaseApp = initializeApp(JSON.parse(jsonConfig)) const messaging = getMessaging(firebaseApp) + function isChrome() { return navigator.userAgent.toLowerCase().includes("chrome") } + onBackgroundMessage(messaging, (payload) => { const notificationTitle = payload.data.title let notificationOptions = { @@ -24,7 +35,7 @@ onBackgroundMessage(messaging, (payload) => { notificationOptions["actions"] = [ { action: payload.data.click_action, - title: "View details", + title: "View Details", }, ] } @@ -41,3 +52,6 @@ if (isChrome()) { } }) } + +self.skipWaiting() +clientsClaim() diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 76616499db..87f0e4908c 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -14,8 +14,12 @@ export default defineConfig({ vue(), VitePWA({ registerType: "autoUpdate", + strategies: "injectManifest", + injectRegister: null, devOptions: { enabled: true, + type: "module", + navigateFallback: "index.html", }, manifest: { display: "standalone", @@ -23,6 +27,7 @@ export default defineConfig({ short_name: "Frappe HR", start_url: "/hrms", description: "Everyday HR & Payroll operations at your fingertips", + theme_color: "#ffffff", icons: [ { src: "/assets/hrms/manifest/manifest-icon-192.maskable.png", From b301e17fa2eee9a9d68a02515ea13c56fa0488a6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Feb 2024 17:33:37 +0530 Subject: [PATCH 15/79] chore: fix dependencies (cherry picked from commit 59cd815cfb0dee4428657e6e0c1647ed1b264eed) --- frontend/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index c62e02a0f9..0fa6b5a91f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,8 +19,6 @@ "frappe-ui": "^0.1.18", "vue": "^3.2.25", "vue-router": "^4.0.12", - "vite": "^4.5.0", - "vite-plugin-pwa": "^0.16.6", "autoprefixer": "^10.4.2", "postcss": "^8.4.5", "eslint": "^8.39.0", From 8e1c35e3ecc4650a87b72137f0e2737ef4b6aef7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 26 Feb 2024 23:08:39 +0530 Subject: [PATCH 16/79] refactor: replace firebase namespaced API with dependency & register sw as classic instead of module - module type is not very reliable and the sw often stops running (cherry picked from commit 6fea7ce86b18998b25a394e02a9890024e4918ed) # Conflicts: # frontend/package.json --- frontend/index.html | 2 +- frontend/package.json | 5 + frontend/public/frappe-push-notification.js | 8 +- frontend/public/sw.js | 7 +- frontend/vite.config.js | 2 - frontend/yarn.lock | 632 +++++++++++++++++++- 6 files changed, 640 insertions(+), 16 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index fc8958a1d2..dbdc71f609 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -188,7 +188,7 @@ .then((url) => { navigator.serviceWorker .register(url, { - type: "module", + type: "classic", }) .then((registration) => { window.frappePushNotification diff --git a/frontend/package.json b/frontend/package.json index 0fa6b5a91f..5dd0dc7b93 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,12 @@ "vite": "^5.1.4", "vite-plugin-pwa": "^0.19.0", "workbox-precaching": "^7.0.0", +<<<<<<< HEAD "workbox-core": "^7.0.0" >>>>>>> a4d7c7749 (chore: add workbox dependencies) +======= + "workbox-core": "^7.0.0", + "firebase": "^10.8.0" +>>>>>>> 6fea7ce86 (refactor: replace firebase namespaced API with dependency & register sw as classic instead of module) } } diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 35fabd45eb..1d4732c68f 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -1,11 +1,11 @@ -import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js" +import { initializeApp } from "firebase/app" import { getMessaging, getToken, - onMessage as onFCMMessage, isSupported, deleteToken, -} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging.js" + onMessage as onFCMMessage, +} from "firebase/messaging" class FrappePushNotification { static relayServerBaseURL = "http://notification-relay:8003" @@ -59,7 +59,7 @@ class FrappePushNotification { this.serviceWorkerRegistration = serviceWorkerRegistration const config = await this.fetchWebConfig() this.messaging = getMessaging(initializeApp(config)) - // this.onMessage(this.onMessageHandler) + this.onMessage(this.onMessageHandler) this.initialized = true } diff --git a/frontend/public/sw.js b/frontend/public/sw.js index cfdad387cd..6c4d7c4c5c 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,11 +1,8 @@ import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching" import { clientsClaim } from "workbox-core" -import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js" -import { - getMessaging, - onBackgroundMessage, -} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-sw.js" +import { initializeApp } from "firebase/app" +import { getMessaging, onBackgroundMessage } from "firebase/messaging/sw" // Use the precache manifest generated by Vite precacheAndRoute(self.__WB_MANIFEST) diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 87f0e4908c..291dfd5828 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -18,8 +18,6 @@ export default defineConfig({ injectRegister: null, devOptions: { enabled: true, - type: "module", - navigateFallback: "index.html", }, manifest: { display: "standalone", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9f71a9b5d1..6f389c65ba 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1073,6 +1073,402 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.41.0.tgz#080321c3b68253522f7646b55b577dd99d2950b3" integrity sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA== +"@fastify/busboy@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff" + integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA== + +"@firebase/analytics-compat@0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.7.tgz#affad547d6db9c13424950df972019fb0e2ecaeb" + integrity sha512-17VCly4P0VFBDqaaal7m1nhyYQwsygtaTpSsnc51sFPRrr9XIYtnD8ficon9fneEGEoJQ2g7OtASvhwX9EbK8g== + dependencies: + "@firebase/analytics" "0.10.1" + "@firebase/analytics-types" "0.8.0" + "@firebase/component" "0.6.5" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/analytics-types@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.0.tgz#551e744a29adbc07f557306530a2ec86add6d410" + integrity sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw== + +"@firebase/analytics@0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.1.tgz#97d750020c5b3b41fd5191074c683a7a8c8900a5" + integrity sha512-5mnH1aQa99J5lZMJwTNzIoRc4yGXHf+fOn+EoEWhCDA3XGPweGHcylCbqq+G1wVJmfILL57fohDMa8ftMZ+44g== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/installations" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/app-check-compat@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.9.tgz#c67caa1cd5043fecab7f8ba1bc45ab047210ad83" + integrity sha512-7LxyupQ8XeEHRh72mO+tqm69kHT6KbWi2KtFMGedJ6tNbwzFzojcXESMKN8RpADXbYoQgY3loWMJjMx4r2Zt7w== + dependencies: + "@firebase/app-check" "0.8.2" + "@firebase/app-check-types" "0.5.0" + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/app-check-interop-types@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz#b27ea1397cb80427f729e4bbf3a562f2052955c4" + integrity sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg== + +"@firebase/app-check-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.0.tgz#1b02826213d7ce6a1cf773c329b46ea1c67064f4" + integrity sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ== + +"@firebase/app-check@0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.2.tgz#9ede3558cc7dc1ac8206a772ba692e67daf7e65e" + integrity sha512-A2B5+ldOguYAeqW1quFN5qNdruSNRrg4W59ag1Eq6QzxuHNIkrE+TrapfrW/z5NYFjCxAYqr/unVCgmk80Dwcg== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/app-compat@0.2.27": + version "0.2.27" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.27.tgz#3dcf08e7c6258ef3b56929060db9d6b1a3c35eb7" + integrity sha512-SYlqocfUDKPHR6MSFC8hree0BTiWFu5o8wbf6zFlYXyG41w7TcHp4wJi4H/EL5V6cM4kxwruXTJtqXX/fRAZtw== + dependencies: + "@firebase/app" "0.9.27" + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/app-types@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.0.tgz#35b5c568341e9e263b29b3d2ba0e9cfc9ec7f01e" + integrity sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q== + +"@firebase/app@0.9.27": + version "0.9.27" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.9.27.tgz#d50d1f99b88fb6c1358bed9703d204dc81185eb9" + integrity sha512-p2Dvl1ge4kRsyK5+wWcmdAIE9MSwZ0pDKAYB51LZgZuz6wciUZk4E1yAEdkfQlRxuHehn+Ol9WP5Qk2XQZiHGg== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/auth-compat@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.2.tgz#c78249fdfa8ef5d7e9c3d8bc3eb8e99d59f0b5dd" + integrity sha512-pRgje5BPCNR1vXyvGOVXwOHtv88A2WooXfklI8sV7/jWi03ExFqNfpJT26GUo/oD39NoKJ3Kt6rD5gVvdV7lMw== + dependencies: + "@firebase/auth" "1.6.0" + "@firebase/auth-types" "0.12.0" + "@firebase/component" "0.6.5" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + undici "5.26.5" + +"@firebase/auth-interop-types@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz#78884f24fa539e34a06c03612c75f222fcc33742" + integrity sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg== + +"@firebase/auth-types@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.0.tgz#f28e1b68ac3b208ad02a15854c585be6da3e8e79" + integrity sha512-pPwaZt+SPOshK8xNoiQlK5XIrS97kFYc3Rc7xmy373QsOJ9MmqXxLaYssP5Kcds4wd2qK//amx/c+A8O2fVeZA== + +"@firebase/auth@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.6.0.tgz#4999c5749922593f2a886d07ec69b453447b0d40" + integrity sha512-Qhl35eJTV6BwvuueTPCY6x8kUlYyzALtjp/Ws0X3fw3AnjVVfuVb7oQ3Xh5VPVfMFhaIuUAd1KXwcAuIklkSDw== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + undici "5.26.5" + +"@firebase/component@0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.5.tgz#8cc7334f2081d700f2769caaa8dae3ac4c1fe37e" + integrity sha512-2tVDk1ixi12sbDmmfITK8lxSjmcb73BMF6Qwc3U44hN/J1Fi1QY/Hnnb6klFlbB9/G16a3J3d4nXykye2EADTw== + dependencies: + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/database-compat@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.3.tgz#f7a255af6208d2d4d7af10ec2c9ecd9af4ff52d5" + integrity sha512-7tHEOcMbK5jJzHWyphPux4osogH/adWwncxdMxdBpB9g1DNIyY4dcz1oJdlkXGM/i/AjUBesZsd5CuwTRTBNTw== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/database" "1.0.3" + "@firebase/database-types" "1.0.1" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/database-types@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.1.tgz#1e7cd9fec03f6ca772c019d839cc72d9b2eda63c" + integrity sha512-Tmcmx5XgiI7UVF/4oGg2P3AOTfq3WKEPsm2yf+uXtN7uG/a4WTWhVMrXGYRY2ZUL1xPxv9V33wQRJ+CcrUhVXw== + dependencies: + "@firebase/app-types" "0.9.0" + "@firebase/util" "1.9.4" + +"@firebase/database@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.3.tgz#88caee93188d28aca355236e9ad69f373f628804" + integrity sha512-9fjqLt9JzL46gw9+NRqsgQEMjgRwfd8XtzcKqG+UYyhVeFCdVRQ0Wp6Dw/dvYHnbH5vNEKzNv36dcB4p+PIAAA== + dependencies: + "@firebase/app-check-interop-types" "0.3.0" + "@firebase/auth-interop-types" "0.2.1" + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + faye-websocket "0.11.4" + tslib "^2.1.0" + +"@firebase/firestore-compat@0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.25.tgz#6f83871314ea7474a084e1840d6b0d4891a1523f" + integrity sha512-+xI7WmsgZCBhMn/+uhDKcg+lsOUJ9FJyt5PGTzkFPbCsozWfeQZ7eVnfPh0rMkUOf0yIQ924RIe04gwvEIbcoQ== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/firestore" "4.4.2" + "@firebase/firestore-types" "3.0.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/firestore-types@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.0.tgz#f3440d5a1cc2a722d361b24cefb62ca8b3577af3" + integrity sha512-Meg4cIezHo9zLamw0ymFYBD4SMjLb+ZXIbuN7T7ddXN6MGoICmOTq3/ltdCGoDCS2u+H1XJs2u/cYp75jsX9Qw== + +"@firebase/firestore@4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.4.2.tgz#21c73bf43d008647f16f8247894b3cacc2afaeb1" + integrity sha512-YaX6ypa/RzU6OkxzUQlpSxwhOIWdTraCNz7sMsbaSEjjl/pj/QvX6TqjkdWGzuBYh2S6rz7ErhDO0g39oZZw/g== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + "@firebase/webchannel-wrapper" "0.10.5" + "@grpc/grpc-js" "~1.9.0" + "@grpc/proto-loader" "^0.7.8" + tslib "^2.1.0" + undici "5.26.5" + +"@firebase/functions-compat@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.7.tgz#a3500b58abe2afcb7a88ca4c07a3a519c9ed5e5f" + integrity sha512-uXe6Kmku5lNogp3OpPBcOJbSvnaCOn+YxS3zlXKNU6Q/NLwcvO3RY1zwYyctCos2RemEw3KEQ7YdzcECXjHWLw== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/functions" "0.11.1" + "@firebase/functions-types" "0.6.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/functions-types@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.0.tgz#ccd7000dc6fc668f5acb4e6a6a042a877a555ef2" + integrity sha512-hfEw5VJtgWXIRf92ImLkgENqpL6IWpYaXVYiRkFY1jJ9+6tIhWM7IzzwbevwIIud/jaxKVdRzD7QBWfPmkwCYw== + +"@firebase/functions@0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.11.1.tgz#fdb129b84a1a44bc705d78220adda2e765b295ee" + integrity sha512-3uUa1hB79Gmy6E1gHTfzoHeZolBeHc/I/n3+lOCDe6BOos9AHmzRjKygcFE/7VA2FJjitCE0K+OHI6+OuoY8fQ== + dependencies: + "@firebase/app-check-interop-types" "0.3.0" + "@firebase/auth-interop-types" "0.2.1" + "@firebase/component" "0.6.5" + "@firebase/messaging-interop-types" "0.2.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + undici "5.26.5" + +"@firebase/installations-compat@0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.5.tgz#e23ff86dc5a4b856f5f3d3abafeda7362daa38c5" + integrity sha512-usvoIaog5CHEw082HXLrKAZ1qd4hIC3N/LDe2NqBgI3pkGE/7auLVM4Gn5gvyryp0x8z/IP1+d9fkGUj2OaGLQ== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/installations" "0.6.5" + "@firebase/installations-types" "0.5.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/installations-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.0.tgz#2adad64755cd33648519b573ec7ec30f21fb5354" + integrity sha512-9DP+RGfzoI2jH7gY4SlzqvZ+hr7gYzPODrbzVD82Y12kScZ6ZpRg/i3j6rleto8vTFC8n6Len4560FnV1w2IRg== + +"@firebase/installations@0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.5.tgz#1d6e0a581747bfaca54f11bf722e1f3da00dcc9c" + integrity sha512-0xxnQWw8rSRzu0ZOCkZaO+MJ0LkDAfwwTB2Z1SxRK6FAz5xkxD1ZUwM0WbCRni49PKubCrZYOJ6yg7tSjU7AKA== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/util" "1.9.4" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/logger@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.0.tgz#15ecc03c452525f9d47318ad9491b81d1810f113" + integrity sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA== + dependencies: + tslib "^2.1.0" + +"@firebase/messaging-compat@0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.6.tgz#ea89934bff5f048576dc1c4ce87e0c4c2141829b" + integrity sha512-Q2xC1s4L7Vpss7P7Gy6GuIS+xmJrf/vm9+gX76IK1Bo1TjoKwleCLHt1LHkPz5Rvqg5pTgzzI8qqPhBpZosFCg== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/messaging" "0.12.6" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/messaging-interop-types@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.0.tgz#6056f8904a696bf0f7fdcf5f2ca8f008e8f6b064" + integrity sha512-ujA8dcRuVeBixGR9CtegfpU4YmZf3Lt7QYkcj693FFannwNuZgfAYaTmbJ40dtjB81SAu6tbFPL9YLNT15KmOQ== + +"@firebase/messaging@0.12.6": + version "0.12.6" + resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.6.tgz#ac7c59ed39a89e00990e3b6dfd7929e13dd77563" + integrity sha512-IORsPp9IPWq4j4yEhTOZ6GAGi3gQwGc+4yexmTAlya+qeBRSdRnJg2iIU/aj+tcKDQYr9RQuQPgHHOdFIx//vA== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/installations" "0.6.5" + "@firebase/messaging-interop-types" "0.2.0" + "@firebase/util" "1.9.4" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.5.tgz#9b827b1801fca19d8c379792326c076877ac5b91" + integrity sha512-jJwJkVyDcIMBaVGrZ6CRGs4m5FCZsWB5QCWYI3FdsHyIa9/TfteNDilxj9wGciF2naFIHDW7TgE69U5dAH9Ktg== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/performance" "0.6.5" + "@firebase/performance-types" "0.2.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/performance-types@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.0.tgz#400685f7a3455970817136d9b48ce07a4b9562ff" + integrity sha512-kYrbr8e/CYr1KLrLYZZt2noNnf+pRwDq2KK9Au9jHrBMnb0/C9X9yWSXmZkFt4UIdsQknBq8uBB7fsybZdOBTA== + +"@firebase/performance@0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.5.tgz#5255fb18329719bc1fb2db29262e5ec15cbace06" + integrity sha512-OzAGcWhOqEFH9GdwUuY0oC5FSlnMejcnmSAhR+EjpI7exdDvixyLyCR4txjSHYNTbumrFBG+EP8GO11CNXRaJA== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/installations" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/remote-config-compat@0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.5.tgz#b6850a45567db5372778668c796a8d49723413f3" + integrity sha512-ImkNnLuGrD/bylBHDJigSY6LMwRrwt37wQbsGZhWG4QQ6KLzHzSf0nnFRRFvkOZodEUE57Ib8l74d6Yn/6TDUQ== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/remote-config" "0.4.5" + "@firebase/remote-config-types" "0.3.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/remote-config-types@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.3.0.tgz#689900dcdb3e5c059e8499b29db393e4e51314b4" + integrity sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA== + +"@firebase/remote-config@0.4.5": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.5.tgz#1aae1a4639bb0dddc14671c10dcd92c8a83c58c1" + integrity sha512-rGLqc/4OmxrS39RA9kgwa6JmgWytQuMo+B8pFhmGp3d++x2Hf9j+MLQfhOLyyUo64fNw20J19mLXhrXvKHsjZQ== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/installations" "0.6.5" + "@firebase/logger" "0.4.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/storage-compat@0.3.4": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.4.tgz#de261a0554747f558584c1465e17e6741da00ec8" + integrity sha512-Y0m5e2gS/wB9Ioth2X/Sgz76vcxvqgQrCmfa9qwhss/N31kxY2Gks6Frv0nrE18AjVfcSmcfDitqUwxcMOTRSg== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/storage" "0.12.1" + "@firebase/storage-types" "0.8.0" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + +"@firebase/storage-types@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.0.tgz#f1e40a5361d59240b6e84fac7fbbbb622bfaf707" + integrity sha512-isRHcGrTs9kITJC0AVehHfpraWFui39MPaU7Eo8QfWlqW7YPymBmRgjDrlOgFdURh6Cdeg07zmkLP5tzTKRSpg== + +"@firebase/storage@0.12.1": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.12.1.tgz#df0b914ba388d20ef9967682c9c67d79fed01777" + integrity sha512-KJ5NV7FUh54TeTlEjdkTTX60ciCKOp9EqlbLnpdcXUYRJg0Z4810TXbilPc1z7fTIG4iPjtdi95bGE9n4dBX8A== + dependencies: + "@firebase/component" "0.6.5" + "@firebase/util" "1.9.4" + tslib "^2.1.0" + undici "5.26.5" + +"@firebase/util@1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.4.tgz#68eee380ab7e7828ec0d8684c46a1abed2d7e334" + integrity sha512-WLonYmS1FGHT97TsUmRN3qnTh5TeeoJp1Gg5fithzuAgdZOUtsYECfy7/noQ3llaguios8r5BuXSEiK82+UrxQ== + dependencies: + tslib "^2.1.0" + +"@firebase/webchannel-wrapper@0.10.5": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.10.5.tgz#cd9897680d0a2f1bce8d8c23a590e5874f4617c5" + integrity sha512-eSkJsnhBWv5kCTSU1tSUVl9mpFu+5NXXunZc83le8GMjMlsWwQArSc7cJJ4yl+aDFY0NGLi0AjZWMn1axOrkRg== + +"@grpc/grpc-js@~1.9.0": + version "1.9.14" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.14.tgz#236378822876cbf7903f9d61a0330410e8dcc5a1" + integrity sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw== + dependencies: + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.8": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" + integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.4" + yargs "^17.7.2" + "@headlessui/vue@^1.7.14": version "1.7.16" resolved "https://registry.yarnpkg.com/@headlessui/vue/-/vue-1.7.16.tgz#bdc9d32d329248910325539b99e6abfce0c69f89" @@ -1232,6 +1628,59 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7" integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@remirror/core-constants@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-2.0.1.tgz#19b4ae221880762cd98452f44288fcc66baaec0f" @@ -1664,6 +2113,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.1.tgz#de559d4b33be9a808fd43372ccee822c70f39704" integrity sha512-DqJociPbZP1lbZ5SQPk4oag6W7AyaGMO6gSfRwq3PWl4PXTwJpRQJhDq4W0kzrg3w6tJ1SwlvGZ5uKFHY13LIg== +"@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "20.11.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659" + integrity sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg== + dependencies: + undici-types "~5.26.4" + "@types/object.omit@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/object.omit/-/object.omit-3.0.0.tgz#0d31e1208eac8fe2ad5c9499a1016a8273bbfafc" @@ -1860,7 +2316,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2079,6 +2535,15 @@ classnames@^2.2.5: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2243,6 +2708,11 @@ electron-to-chromium@^1.4.284: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.402.tgz#9aa7bbb63081513127870af6d22f829344c5ba57" integrity sha512-gWYvJSkohOiBE6ecVYXkrDgNaUjo47QEKK0kQzmWyhkH+yoYiG44bwuicTGNSIQRG3WDMsWVZJLRnJnLNkbWvA== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + engine.io-client@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91" @@ -2524,6 +2994,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +faye-websocket@0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + feather-icons@^4.28.0: version "4.29.0" resolved "https://registry.yarnpkg.com/feather-icons/-/feather-icons-4.29.0.tgz#4e40e3cbb7bf359ffbbf700edbebdde4e4a74ab6" @@ -2561,6 +3038,38 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +firebase@^10.8.0: + version "10.8.0" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.8.0.tgz#764fa98b5699ca40dfb604df21747b836a101fa3" + integrity sha512-UJpC24vw8JFuHEOQyArBGKTUd7+kohLISCzHyn0M/prP0KOTx2io1eyLliEid330QqnWI7FOlPxoU97qecCSfQ== + dependencies: + "@firebase/analytics" "0.10.1" + "@firebase/analytics-compat" "0.2.7" + "@firebase/app" "0.9.27" + "@firebase/app-check" "0.8.2" + "@firebase/app-check-compat" "0.3.9" + "@firebase/app-compat" "0.2.27" + "@firebase/app-types" "0.9.0" + "@firebase/auth" "1.6.0" + "@firebase/auth-compat" "0.5.2" + "@firebase/database" "1.0.3" + "@firebase/database-compat" "1.0.3" + "@firebase/firestore" "4.4.2" + "@firebase/firestore-compat" "0.3.25" + "@firebase/functions" "0.11.1" + "@firebase/functions-compat" "0.3.7" + "@firebase/installations" "0.6.5" + "@firebase/installations-compat" "0.2.5" + "@firebase/messaging" "0.12.6" + "@firebase/messaging-compat" "0.2.6" + "@firebase/performance" "0.6.5" + "@firebase/performance-compat" "0.2.5" + "@firebase/remote-config" "0.4.5" + "@firebase/remote-config-compat" "0.2.5" + "@firebase/storage" "0.12.1" + "@firebase/storage-compat" "0.3.4" + "@firebase/util" "1.9.4" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -2669,6 +3178,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" @@ -2812,12 +3326,17 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + idb-keyval@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== -idb@^7.0.1: +idb@7.1.1, idb@^7.0.1: version "7.1.1" resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== @@ -2931,6 +3450,11 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -3171,6 +3695,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + lodash.castarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" @@ -3201,6 +3730,11 @@ lodash@^4.17.20, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -3712,6 +4246,24 @@ prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, pros prosemirror-state "^1.0.0" prosemirror-transform "^1.1.0" +protobufjs@^7.2.4: + version "7.2.6" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215" + integrity sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" @@ -3795,6 +4347,11 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" @@ -3877,7 +4434,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@^5.1.0: +safe-buffer@>=5.1.0, safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -3986,6 +4543,15 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string.prototype.matchall@^4.0.6: version "4.0.8" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" @@ -4036,7 +4602,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4245,6 +4811,18 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici@5.26.5: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.5.tgz#f6dc8c565e3cad8c4475b187f51a13e505092838" + integrity sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw== + dependencies: + "@fastify/busboy" "^2.0.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -4373,6 +4951,20 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -4575,6 +5167,15 @@ workbox-window@7.0.0, workbox-window@^7.0.0: "@types/trusted-types" "^2.0.2" workbox-core "7.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -4595,6 +5196,11 @@ xmlhttprequest-ssl@~2.0.0: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -4610,6 +5216,24 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From bb44c576439ca83c03bedd590dc7d0de6f998aa0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 27 Feb 2024 13:49:03 +0530 Subject: [PATCH 17/79] feat: use `showNotification` for in-app notifications (cherry picked from commit 339097b35e7a2ed5c4eb6b5bb44589a7b670e10d) --- frontend/public/frappe-push-notification.js | 1 - frontend/src/App.vue | 10 +++++-- .../src/components/FrappeNotification.vue | 29 ------------------- frontend/src/utils/pushNotifications.js | 28 ++++++++++++++++++ 4 files changed, 36 insertions(+), 32 deletions(-) delete mode 100644 frontend/src/components/FrappeNotification.vue create mode 100644 frontend/src/utils/pushNotifications.js diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 1d4732c68f..136dbf1a38 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -122,7 +122,6 @@ class FrappePushNotification { * )} callback - Callback function to handle message */ onMessage(callback) { - console.log("onMessage") if (callback == null) return this.onMessageHandler = callback if (this.messaging == null) return diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2cc9531537..37dc2b6efb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,15 +4,21 @@ - diff --git a/frontend/src/components/FrappeNotification.vue b/frontend/src/components/FrappeNotification.vue deleted file mode 100644 index 8d0835ab57..0000000000 --- a/frontend/src/components/FrappeNotification.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - diff --git a/frontend/src/utils/pushNotifications.js b/frontend/src/utils/pushNotifications.js new file mode 100644 index 0000000000..1dc03b1a36 --- /dev/null +++ b/frontend/src/utils/pushNotifications.js @@ -0,0 +1,28 @@ +export const isChrome = () => + navigator.userAgent.toLowerCase().includes("chrome") + +export const showNotification = (payload) => { + const registration = window.frappePushNotification.serviceWorkerRegistration + if (!registration) return + + const notificationTitle = payload?.data?.title + const notificationOptions = { + body: payload?.data?.body || "", + } + if (isChrome()) { + notificationOptions["data"] = { + url: payload?.data?.click_action, + } + } else { + if (payload?.data?.click_action) { + notificationOptions["actions"] = [ + { + action: payload.data.click_action, + title: "View Details", + }, + ] + } + } + + registration.showNotification(notificationTitle, notificationOptions) +} From 815ca07ee58a1d6c7a60239cee6290581b72cd5e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 27 Feb 2024 18:30:25 +0530 Subject: [PATCH 18/79] fix: check if browser supports push/service worker (cherry picked from commit 19092a2843461fb46b56a7db067aac6cd2db13bd) --- frontend/index.html | 43 +++++++++++---------- frontend/public/frappe-push-notification.js | 2 +- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index dbdc71f609..40a6864d79 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -182,26 +182,29 @@ import FrappePushNotification from "./public/frappe-push-notification" window.frappePushNotification = new FrappePushNotification("hrms") - document.addEventListener("DOMContentLoaded", () => { - window.frappePushNotification - .appendConfigToServiceWorkerURL("/assets/hrms/frontend/sw.js") - .then((url) => { - navigator.serviceWorker - .register(url, { - type: "classic", - }) - .then((registration) => { - window.frappePushNotification - .initialize(registration) - .then(() => { - console.log("Frappe Push Notification initialized") - }) - }) - }) - .catch((err) => { - console.error(err) - }) - }) + + if ("serviceWorker" in navigator) { + document.addEventListener("DOMContentLoaded", () => { + window.frappePushNotification + .appendConfigToServiceWorkerURL("/assets/hrms/frontend/sw.js") + .then((url) => { + navigator.serviceWorker + .register(url, { + type: "classic", + }) + .then((registration) => { + window.frappePushNotification + .initialize(registration) + .then(() => { + console.log("Frappe Push Notification initialized") + }) + }) + }) + .catch((err) => { + console.error("Failed to register service worker", err) + }) + }) + } diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 136dbf1a38..ee73d28fb0 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -145,7 +145,7 @@ class FrappePushNotification { */ async enableNotification() { if (!(await isSupported())) { - throw new Error("Push notification not supported") + throw new Error("Push notifications are not supported on your device") } // Return if token already presence in the instance if (this.token != null) { From cfa147c9188e577e3b19616f520d1bb385ca9d0a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 27 Feb 2024 18:31:06 +0530 Subject: [PATCH 19/79] fix: show actual error messages while enabling/disabling push (cherry picked from commit ff58cf20789dd80143e3ca0c7452748d5c898b86) --- frontend/src/views/AppSettings.vue | 31 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/frontend/src/views/AppSettings.vue b/frontend/src/views/AppSettings.vue index c4fd2a0f2c..3634cc417e 100644 --- a/frontend/src/views/AppSettings.vue +++ b/frontend/src/views/AppSettings.vue @@ -54,6 +54,7 @@ const togglePushNotifications = (newValue) => { .disableNotification() .then((data) => { pushNotificationState.value = false // Disable the switch + // TODO: add commonfied toast util for success and error messages toast({ title: "Success", text: "Push notifications disabled", @@ -62,12 +63,11 @@ const togglePushNotifications = (newValue) => { iconClasses: "text-green-500", }) }) - .catch((err) => { - console.log(err) + .catch((error) => { toast({ title: "Error", - text: "Something went wrong", - icon: "error", + text: error.message, + icon: "alert-circle", position: "bottom-center", iconClasses: "text-red-500", }) @@ -80,18 +80,27 @@ const enablePushNotifications = () => { .enableNotification() .then((data) => { console.log(data) - let permission_granted = data.permission_granted - let token = data.token - if (permission_granted) { - alert("Notification Activated") + if (data.permission_granted) { pushNotificationState.value = true } else { - alert("Permission Denied ! Retry again later") + toast({ + title: "Error", + text: "Push Notification permission denied", + icon: "alert-circle", + position: "bottom-center", + iconClasses: "text-red-500", + }) pushNotificationState.value = false } }) - .catch((err) => { - console.log(err) + .catch((error) => { + toast({ + title: "Error", + text: error.message, + icon: "alert-circle", + position: "bottom-center", + iconClasses: "text-red-500", + }) pushNotificationState.value = false }) } From 12ef7614178463d4bb8eaa59027849bd9cb470c5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 27 Feb 2024 18:31:39 +0530 Subject: [PATCH 20/79] feat: redirect user to the exact ref document (cherry picked from commit 9c0cb2f321a8e08da43d9c18afc6622ca8105305) --- .../pwa_notification/pwa_notification.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/hrms/hr/doctype/pwa_notification/pwa_notification.py b/hrms/hr/doctype/pwa_notification/pwa_notification.py index cd5792425f..61ef44b394 100644 --- a/hrms/hr/doctype/pwa_notification/pwa_notification.py +++ b/hrms/hr/doctype/pwa_notification/pwa_notification.py @@ -16,12 +16,23 @@ def after_insert(self): def send_push_notification(self): try: - url = frappe.utils.get_url() - push_notification = PushNotification("hrms") if push_notification.is_enabled(): push_notification.send_notification_to_user( - self.to_user, self.reference_document_type, self.message, link=url, truncate_body=True + self.to_user, + self.reference_document_type, + self.message, + link=self.get_notification_link(), ) except Exception: self.log_error(f"Error sending push notification: {self.name}") + + def get_notification_link(self): + base_url = f"{frappe.utils.get_url()}/hrms" + + if self.reference_document_type == "Leave Application": + return f"{base_url}/leave-applications/{self.reference_document_name}" + elif self.reference_document_type == "Expense Claim": + return f"{base_url}/expense-claims/{self.reference_document_name}" + + return base_url From e1525b9be115c7f5e1298042a9b286afa84b48d1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 27 Feb 2024 21:33:17 +0530 Subject: [PATCH 21/79] feat: show loading indicator while toggling push notifications (cherry picked from commit 402f176c4c7c3d0ceabefb98f5fbaa3bdb190a13) --- frontend/src/views/AppSettings.vue | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/AppSettings.vue b/frontend/src/views/AppSettings.vue index 3634cc417e..9eb129825b 100644 --- a/frontend/src/views/AppSettings.vue +++ b/frontend/src/views/AppSettings.vue @@ -24,9 +24,21 @@ size="md" label="Enable Push Notifications" :model-value="pushNotificationState" + :disabled="isLoading" @update:model-value="togglePushNotifications" /> + +
+ + + {{ pushNotificationState ? "Disabling" : "Enabling" }} Push + Notifications... + +
@@ -37,7 +49,7 @@ From 2f5e1253f338c2de5a12f07df20eca6de93c5cb7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 27 Feb 2024 22:54:21 +0530 Subject: [PATCH 22/79] feat: link settings in profile and notifications pages (cherry picked from commit bf26d56090604eb0bcb7752f8c04713f2979275d) --- frontend/src/views/Notifications.vue | 43 +++++++++++++++++----------- frontend/src/views/Profile.vue | 25 ++++++++++++++++ 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/frontend/src/views/Notifications.vue b/frontend/src/views/Notifications.vue index 57ec2d0f5f..66183f8b05 100644 --- a/frontend/src/views/Notifications.vue +++ b/frontend/src/views/Notifications.vue @@ -19,24 +19,35 @@
-
-
+
+
{{ unreadNotificationsCount.data }} Unread
- +
+ + +
+
+ +
+
+ +
+ +
+ Settings +
+
+ +
+
+
+
@@ -51,7 +53,7 @@ import { IonPage, IonContent } from "@ionic/vue" import { useRouter } from "vue-router" import { FeatherIcon, Switch, toast, LoadingIndicator } from "frappe-ui" -import { ref } from "vue" +import { computed, ref } from "vue" const router = useRouter() const pushNotificationState = ref( @@ -59,6 +61,19 @@ const pushNotificationState = ref( ) const isLoading = ref(false) +const disablePushSetting = computed(() => { + return ( + !(window.push_relay_server_url && window.push_notifications_enabled) || + isLoading.value + ) +}) + +const description = computed(() => { + return disablePushSetting.value + ? "Push notifications have been disabled on your site" + : "" +}) + const togglePushNotifications = (newValue) => { if (newValue) { enablePushNotifications() diff --git a/frontend/src/views/Notifications.vue b/frontend/src/views/Notifications.vue index 66183f8b05..efb0e2bfdc 100644 --- a/frontend/src/views/Notifications.vue +++ b/frontend/src/views/Notifications.vue @@ -28,6 +28,7 @@
-
+
diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 98140919a3..06549dae6f 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -9,11 +9,11 @@ import { class FrappePushNotification { static get relayServerBaseURL() { - return window.push_relay_server_url + return window.frappe?.boot.push_relay_server_url } static get isNotificationRelayEnabled() { - return window.push_notifications_enabled + return window.frappe?.boot.push_notifications_enabled } // Type definitions diff --git a/frontend/src/main.js b/frontend/src/main.js index 4b8e7a1a74..6172455f32 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -86,9 +86,8 @@ router.isReady().then(() => { frappeRequest({ url: "/api/method/hrms.www.hrms.get_context_for_dev", }).then((values) => { - for (let key in values) { - window[key] = values[key] - } + if (!window.frappe) window.frappe = {} + window.frappe.boot = values registerServiceWorker() app.mount("#app") }) From 5e9ee3f6adc481f0ce31cf687c89a5e21d509977 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 3 Mar 2024 02:42:32 +0530 Subject: [PATCH 29/79] refactor: fetch push notification settings from API instead of boot (cherry picked from commit 5526b06da44165e61c500567f8eae06b783e7177) --- frontend/public/frappe-push-notification.js | 4 ---- frontend/src/data/notifications.js | 6 ++++++ frontend/src/views/AppSettings.vue | 8 ++++++-- frontend/src/views/Notifications.vue | 10 ++++++++-- frontend/src/views/Profile.vue | 6 +++++- hrms/api/__init__.py | 6 ++++++ hrms/www/hrms.py | 9 +-------- 7 files changed, 32 insertions(+), 17 deletions(-) diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 06549dae6f..37dcbe21e7 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -12,10 +12,6 @@ class FrappePushNotification { return window.frappe?.boot.push_relay_server_url } - static get isNotificationRelayEnabled() { - return window.frappe?.boot.push_notifications_enabled - } - // Type definitions /** * Web Config diff --git a/frontend/src/data/notifications.js b/frontend/src/data/notifications.js index 5d9b68fa24..7c5916c3d6 100644 --- a/frontend/src/data/notifications.js +++ b/frontend/src/data/notifications.js @@ -27,3 +27,9 @@ export const notifications = createListResource({ unreadNotificationsCount.reload() }, }) + +export const arePushNotificationsEnabled = createResource({ + url: "hrms.api.are_push_notifications_enabled", + cache: "hrms:push_notifications_enabled", + auto: true, +}) diff --git a/frontend/src/views/AppSettings.vue b/frontend/src/views/AppSettings.vue index d0996ac2f2..1ec9a7035b 100644 --- a/frontend/src/views/AppSettings.vue +++ b/frontend/src/views/AppSettings.vue @@ -55,6 +55,8 @@ import { FeatherIcon, Switch, toast, LoadingIndicator } from "frappe-ui" import { computed, ref } from "vue" +import { arePushNotificationsEnabled } from "@/data/notifications" + const router = useRouter() const pushNotificationState = ref( window.frappePushNotification?.isNotificationEnabled() @@ -63,8 +65,10 @@ const isLoading = ref(false) const disablePushSetting = computed(() => { return ( - !(window.push_relay_server_url && window.push_notifications_enabled) || - isLoading.value + !( + window.frappe?.boot.push_relay_server_url && + arePushNotificationsEnabled.data + ) || isLoading.value ) }) diff --git a/frontend/src/views/Notifications.vue b/frontend/src/views/Notifications.vue index efb0e2bfdc..f670509e8c 100644 --- a/frontend/src/views/Notifications.vue +++ b/frontend/src/views/Notifications.vue @@ -96,14 +96,20 @@ import { computed, inject } from "vue" import EmployeeAvatar from "@/components/EmployeeAvatar.vue" import EmptyState from "@/components/EmptyState.vue" -import { unreadNotificationsCount, notifications } from "@/data/notifications" +import { + unreadNotificationsCount, + notifications, + arePushNotificationsEnabled, +} from "@/data/notifications" const user = inject("$user") const dayjs = inject("$dayjs") const router = useRouter() const allowPushNotifications = computed( - () => window.push_relay_server_url && window.push_notifications_enabled + () => + window.frappe?.boot.push_relay_server_url && + arePushNotificationsEnabled.data ) const markAllAsRead = createResource({ diff --git a/frontend/src/views/Profile.vue b/frontend/src/views/Profile.vue index 70b23a8059..1ceff543f4 100644 --- a/frontend/src/views/Profile.vue +++ b/frontend/src/views/Profile.vue @@ -147,6 +147,8 @@ import { formatCurrency } from "@/utils/formatters" import ProfileInfoModal from "@/components/ProfileInfoModal.vue" +import { arePushNotificationsEnabled } from "@/data/notifications" + const DOCTYPE = "Employee" const socket = inject("$socket") @@ -214,7 +216,9 @@ const isInfoModalOpen = ref(false) const selectedItem = ref(null) const allowPushNotifications = computed( - () => window.push_relay_server_url && window.push_notifications_enabled + () => + window.frappe?.boot.push_relay_server_url && + arePushNotificationsEnabled.data ) const openInfoModal = async (request) => { diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 6dee44853e..963621f578 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -75,6 +75,7 @@ def get_all_employees() -> list[dict]: ) +# Notifications @frappe.whitelist() def get_unread_notifications_count() -> int: return frappe.db.count( @@ -94,6 +95,11 @@ def mark_all_notifications_as_read() -> None: ) +@frappe.whitelist() +def are_push_notifications_enabled() -> bool: + return frappe.db.get_single_value("Push Notification Settings", "enable_push_notification_relay") + + # Leaves and Holidays @frappe.whitelist() def get_leave_applications( diff --git a/hrms/www/hrms.py b/hrms/www/hrms.py index cc48559faf..1385c2984b 100644 --- a/hrms/www/hrms.py +++ b/hrms/www/hrms.py @@ -20,11 +20,4 @@ def get_context_for_dev(): def get_boot(): - return frappe._dict( - { - "push_notifications_enabled": frappe.db.get_single_value( - "Push Notification Settings", "enable_push_notification_relay" - ), - "push_relay_server_url": frappe.conf.get("push_relay_server_url"), - } - ) + return frappe._dict({"push_relay_server_url": frappe.conf.get("push_relay_server_url")}) From 248a3aaa61944c01da5d57dac6a27ec511f0a3f6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 3 Mar 2024 02:55:48 +0530 Subject: [PATCH 30/79] fix: push notification setting description visibility (cherry picked from commit 4f9f00c6766ebe4f24514b9782032e275226a5a2) --- frontend/src/views/AppSettings.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/AppSettings.vue b/frontend/src/views/AppSettings.vue index 1ec9a7035b..293d1783c0 100644 --- a/frontend/src/views/AppSettings.vue +++ b/frontend/src/views/AppSettings.vue @@ -73,7 +73,10 @@ const disablePushSetting = computed(() => { }) const description = computed(() => { - return disablePushSetting.value + return !( + window.frappe?.boot.push_relay_server_url && + arePushNotificationsEnabled.data + ) ? "Push notifications have been disabled on your site" : "" }) From 81234ede0c7c1d326c785ca8de7ab933ee871e57 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 3 Mar 2024 03:14:07 +0530 Subject: [PATCH 31/79] chore: fix dev dependencies (cherry picked from commit 7bcd4140e5e2ae8fed7c3e33625e187718f47095) # Conflicts: # frontend/package.json --- frontend/package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/package.json b/frontend/package.json index 5dd0dc7b93..cdc1927048 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,12 +21,15 @@ "vue-router": "^4.0.12", "autoprefixer": "^10.4.2", "postcss": "^8.4.5", +<<<<<<< HEAD "eslint": "^8.39.0", "eslint-plugin-vue": "^9.11.0", "prettier": "^2.8.8", <<<<<<< HEAD "tailwindcss": "^3.0.15" ======= +======= +>>>>>>> 7bcd4140e (chore: fix dev dependencies) "tailwindcss": "^3.0.15", "vite": "^5.1.4", "vite-plugin-pwa": "^0.19.0", @@ -37,6 +40,14 @@ ======= "workbox-core": "^7.0.0", "firebase": "^10.8.0" +<<<<<<< HEAD >>>>>>> 6fea7ce86 (refactor: replace firebase namespaced API with dependency & register sw as classic instead of module) +======= + }, + "devDependencies": { + "eslint": "^8.39.0", + "eslint-plugin-vue": "^9.11.0", + "prettier": "^2.8.8" +>>>>>>> 7bcd4140e (chore: fix dev dependencies) } } From 47ef45d97f4a6b26322dbb6c7c9254d7a1cf1ed0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 3 Mar 2024 03:24:18 +0530 Subject: [PATCH 32/79] fix: handle incompatible frappe version (cherry picked from commit f6ffe47403961a66e1eca0d3d0962c49c3766caa) --- hrms/api/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hrms/api/__init__.py b/hrms/api/__init__.py index 963621f578..99b4563974 100644 --- a/hrms/api/__init__.py +++ b/hrms/api/__init__.py @@ -97,7 +97,11 @@ def mark_all_notifications_as_read() -> None: @frappe.whitelist() def are_push_notifications_enabled() -> bool: - return frappe.db.get_single_value("Push Notification Settings", "enable_push_notification_relay") + try: + return frappe.db.get_single_value("Push Notification Settings", "enable_push_notification_relay") + except frappe.DoesNotExistError: + # push notifications are not supported in the current framework version + return False # Leaves and Holidays From 844da69d29560df0dadf45c2a973affced1667b1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 3 Mar 2024 19:51:09 +0530 Subject: [PATCH 33/79] feat: pass icon for notifications (cherry picked from commit 3c446351dc3cc21c1a7b3059811e857330bf0424) --- frontend/public/sw.js | 3 +++ frontend/src/utils/pushNotifications.js | 3 +++ hrms/hr/doctype/pwa_notification/pwa_notification.py | 1 + 3 files changed, 7 insertions(+) diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 6c4d7c4c5c..038c1a19d5 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -23,6 +23,9 @@ onBackgroundMessage(messaging, (payload) => { let notificationOptions = { body: payload.data.body || "", } + if (payload.data.notification_icon) { + notificationOptions["icon"] = payload.data.notification_icon + } if (isChrome()) { notificationOptions["data"] = { url: payload.data.click_action, diff --git a/frontend/src/utils/pushNotifications.js b/frontend/src/utils/pushNotifications.js index 1dc03b1a36..267f680678 100644 --- a/frontend/src/utils/pushNotifications.js +++ b/frontend/src/utils/pushNotifications.js @@ -9,6 +9,9 @@ export const showNotification = (payload) => { const notificationOptions = { body: payload?.data?.body || "", } + if (payload?.data?.notification_icon) { + notificationOptions["icon"] = payload.data.notification_icon + } if (isChrome()) { notificationOptions["data"] = { url: payload?.data?.click_action, diff --git a/hrms/hr/doctype/pwa_notification/pwa_notification.py b/hrms/hr/doctype/pwa_notification/pwa_notification.py index 914d83a756..fe0f561e1e 100644 --- a/hrms/hr/doctype/pwa_notification/pwa_notification.py +++ b/hrms/hr/doctype/pwa_notification/pwa_notification.py @@ -24,6 +24,7 @@ def send_push_notification(self): self.reference_document_type, self.message, link=self.get_notification_link(), + icon=f"{frappe.utils.get_url()}/assets/hrms/manifest/favicon-196.png", ) except ImportError: # push notifications are not supported in the current framework version From adebb6f41e30bd40a1872f3f18573286a2443353 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 Mar 2024 20:20:11 +0530 Subject: [PATCH 34/79] fix(setup): setup employee custom fields after master setup (cherry picked from commit d4617ec6424b024a45f940949ec3175661f0d9da) --- hrms/setup.py | 196 +++++++++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/hrms/setup.py b/hrms/setup.py index 3d553707e6..92cce6445a 100644 --- a/hrms/setup.py +++ b/hrms/setup.py @@ -33,104 +33,6 @@ def before_uninstall(): def get_custom_fields(): """HR specific custom fields that need to be added to the masters in ERPNext""" return { - "Employee": [ - { - "fieldname": "employment_type", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Employment Type", - "options": "Employment Type", - "insert_after": "department", - }, - { - "fieldname": "job_applicant", - "fieldtype": "Link", - "label": "Job Applicant", - "options": "Job Applicant", - "insert_after": "employment_details", - }, - { - "fieldname": "grade", - "fieldtype": "Link", - "label": "Grade", - "options": "Employee Grade", - "insert_after": "branch", - }, - { - "fieldname": "default_shift", - "fieldtype": "Link", - "label": "Default Shift", - "options": "Shift Type", - "insert_after": "holiday_list", - }, - { - "collapsible": 1, - "fieldname": "health_insurance_section", - "fieldtype": "Section Break", - "label": "Health Insurance", - "insert_after": "health_details", - }, - { - "fieldname": "health_insurance_provider", - "fieldtype": "Link", - "label": "Health Insurance Provider", - "options": "Employee Health Insurance", - "insert_after": "health_insurance_section", - }, - { - "depends_on": "eval:doc.health_insurance_provider", - "fieldname": "health_insurance_no", - "fieldtype": "Data", - "label": "Health Insurance No", - "insert_after": "health_insurance_provider", - }, - { - "fieldname": "approvers_section", - "fieldtype": "Section Break", - "label": "Approvers", - "insert_after": "default_shift", - }, - { - "fieldname": "expense_approver", - "fieldtype": "Link", - "label": "Expense Approver", - "options": "User", - "insert_after": "approvers_section", - }, - { - "fieldname": "leave_approver", - "fieldtype": "Link", - "label": "Leave Approver", - "options": "User", - "insert_after": "expense_approver", - }, - { - "fieldname": "column_break_45", - "fieldtype": "Column Break", - "insert_after": "leave_approver", - }, - { - "fieldname": "shift_request_approver", - "fieldtype": "Link", - "label": "Shift Request Approver", - "options": "User", - "insert_after": "column_break_45", - }, - { - "fieldname": "salary_cb", - "fieldtype": "Column Break", - "insert_after": "salary_mode", - }, - { - "fetch_from": "department.payroll_cost_center", - "fetch_if_empty": 1, - "fieldname": "payroll_cost_center", - "fieldtype": "Link", - "label": "Payroll Cost Center", - "options": "Cost Center", - "insert_after": "salary_cb", - }, - ], "Company": [ { "fieldname": "hr_and_payroll_tab", @@ -257,6 +159,104 @@ def get_custom_fields(): "insert_after": "required_skills_section", }, ], + "Employee": [ + { + "fieldname": "employment_type", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Employment Type", + "options": "Employment Type", + "insert_after": "department", + }, + { + "fieldname": "job_applicant", + "fieldtype": "Link", + "label": "Job Applicant", + "options": "Job Applicant", + "insert_after": "employment_details", + }, + { + "fieldname": "grade", + "fieldtype": "Link", + "label": "Grade", + "options": "Employee Grade", + "insert_after": "branch", + }, + { + "fieldname": "default_shift", + "fieldtype": "Link", + "label": "Default Shift", + "options": "Shift Type", + "insert_after": "holiday_list", + }, + { + "collapsible": 1, + "fieldname": "health_insurance_section", + "fieldtype": "Section Break", + "label": "Health Insurance", + "insert_after": "health_details", + }, + { + "fieldname": "health_insurance_provider", + "fieldtype": "Link", + "label": "Health Insurance Provider", + "options": "Employee Health Insurance", + "insert_after": "health_insurance_section", + }, + { + "depends_on": "eval:doc.health_insurance_provider", + "fieldname": "health_insurance_no", + "fieldtype": "Data", + "label": "Health Insurance No", + "insert_after": "health_insurance_provider", + }, + { + "fieldname": "approvers_section", + "fieldtype": "Section Break", + "label": "Approvers", + "insert_after": "default_shift", + }, + { + "fieldname": "expense_approver", + "fieldtype": "Link", + "label": "Expense Approver", + "options": "User", + "insert_after": "approvers_section", + }, + { + "fieldname": "leave_approver", + "fieldtype": "Link", + "label": "Leave Approver", + "options": "User", + "insert_after": "expense_approver", + }, + { + "fieldname": "column_break_45", + "fieldtype": "Column Break", + "insert_after": "leave_approver", + }, + { + "fieldname": "shift_request_approver", + "fieldtype": "Link", + "label": "Shift Request Approver", + "options": "User", + "insert_after": "column_break_45", + }, + { + "fieldname": "salary_cb", + "fieldtype": "Column Break", + "insert_after": "salary_mode", + }, + { + "fetch_from": "department.payroll_cost_center", + "fetch_if_empty": 1, + "fieldname": "payroll_cost_center", + "fieldtype": "Link", + "label": "Payroll Cost Center", + "options": "Cost Center", + "insert_after": "salary_cb", + }, + ], "Project": [ { "fieldname": "total_expense_claim", From fe47f06711fe140af10c9c799251d0ac2144d210 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 Mar 2024 21:25:26 +0530 Subject: [PATCH 35/79] fix: `fetch_from` config for timesheet (cherry picked from commit 34ea7772b34ae7c6e665e5e39f79c436f3a49888) --- hrms/payroll/doctype/salary_slip/salary_slip.json | 3 +-- .../doctype/salary_slip_timesheet/salary_slip_timesheet.json | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.json b/hrms/payroll/doctype/salary_slip/salary_slip.json index ae5d32dd66..ebb2b84de9 100644 --- a/hrms/payroll/doctype/salary_slip/salary_slip.json +++ b/hrms/payroll/doctype/salary_slip/salary_slip.json @@ -275,7 +275,6 @@ "options": "Salary Slip Timesheet" }, { - "fetch_from": "timesheet.total_hours", "fieldname": "total_working_hours", "fieldtype": "Float", "label": "Total Working Hours", @@ -728,7 +727,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2023-11-07 23:05:43.633760", + "modified": "2024-03-04 19:05:43.633760", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/hrms/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.json b/hrms/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.json index 9930c53ec9..3b3dc6a7af 100644 --- a/hrms/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.json +++ b/hrms/payroll/doctype/salary_slip_timesheet/salary_slip_timesheet.json @@ -18,6 +18,7 @@ "reqd": 1 }, { + "fetch_from": "time_sheet.total_hours", "fieldname": "working_hours", "fieldtype": "Float", "in_list_view": 1, @@ -28,7 +29,7 @@ ], "istable": 1, "links": [], - "modified": "2020-06-22 23:27:43.463532", + "modified": "2024-03-04 19:05:43.633760", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip Timesheet", From 64ecc65f7fc021b1075642f47cb1e97966f05406 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 Mar 2024 23:02:30 +0530 Subject: [PATCH 36/79] chore: fix conflicts --- frontend/package.json | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index cdc1927048..709465ceed 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,33 +21,16 @@ "vue-router": "^4.0.12", "autoprefixer": "^10.4.2", "postcss": "^8.4.5", -<<<<<<< HEAD - "eslint": "^8.39.0", - "eslint-plugin-vue": "^9.11.0", - "prettier": "^2.8.8", -<<<<<<< HEAD - "tailwindcss": "^3.0.15" -======= -======= ->>>>>>> 7bcd4140e (chore: fix dev dependencies) "tailwindcss": "^3.0.15", "vite": "^5.1.4", "vite-plugin-pwa": "^0.19.0", "workbox-precaching": "^7.0.0", -<<<<<<< HEAD - "workbox-core": "^7.0.0" ->>>>>>> a4d7c7749 (chore: add workbox dependencies) -======= "workbox-core": "^7.0.0", "firebase": "^10.8.0" -<<<<<<< HEAD ->>>>>>> 6fea7ce86 (refactor: replace firebase namespaced API with dependency & register sw as classic instead of module) -======= }, "devDependencies": { "eslint": "^8.39.0", "eslint-plugin-vue": "^9.11.0", "prettier": "^2.8.8" ->>>>>>> 7bcd4140e (chore: fix dev dependencies) } } From a13efad5ac0636160fb18749d9c71ee216b8cf8f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:36:11 +0100 Subject: [PATCH 37/79] fix: german translations for "leave" (#1500) --- hrms/translations/de.csv | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/hrms/translations/de.csv b/hrms/translations/de.csv index 92c2411950..07f72a3285 100644 --- a/hrms/translations/de.csv +++ b/hrms/translations/de.csv @@ -28,7 +28,7 @@ Beginner,Anfänger, Birthday Reminder,Geburtstagserinnerung, Bonus Payment Date cannot be a past date,Das Bonuszahlungsdatum kann kein vergangenes Datum sein, Calls,Anrufe, -Cannot find active Leave Period,Aktive Abwesenheitszeit kann nicht gefunden werden, +Cannot find active Leave Period,Aktive Abwesenheitsperiode kann nicht gefunden werden, Casual Leave,Erholungsurlaub, Claimed Amount,Anspruchsbetrag, Compensatory Off,Ausgleich für, @@ -105,8 +105,8 @@ Leave Type {0} cannot be allocated since it is leave without pay,"Abwesenheitsar Leave Type {0} cannot be carry-forwarded,Abwesenheitsart {0} kann nicht in die Zukunft übertragen werden, Leave Type {0} is not encashable,Abwesenheitsart {0} ist nicht umsetzbar, Leave Without Pay,Unbezahlter Urlaub, -"Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Resturlaub bereits in zukünftige Abwesenheitskontingente {1} übertragen wurde, kann der Urlaub nicht vor {0} zugeteilt werden.", -"Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Resturlaub bereits in zukünftige Abwesenheitskontingente {1} übertragen wurde, kann der Urlaub nicht vor {0} genehmigt/abgelehnt werden.", +"Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Abwesenheitssaldo bereits in zukünftige Zuteilungen {1} übertragen wurde, kann die Abwesenheit nicht vor {0} zugeordnet werden.", +"Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}","Da der Abwesenheitssaldo bereits in zukünftige Zuteilungen {1} übertragen wurde, kann die Abwesenheit nicht vor {0} angewendet / storniert werden.", Leave of type {0} cannot be longer than {1},Abwesenheit vom Typ {0} kann nicht länger sein als {1}, Leaves,Abwesenheiten, Leaves Allocated Successfully for {0},Erfolgreich zugewiesene Abwesenheiten für {0}, @@ -136,7 +136,7 @@ No salary slip found to submit for the above selected criteria OR salary slip al Nothing to change,Nichts zu ändern, Notice Period,Mitteilungsfrist, Only Leave Applications with status 'Approved' and 'Rejected' can be submitted,"Nur Abwesenheitsanträge mit dem Status ""Gewährt"" und ""Abgelehnt"" können übermittelt werden.", -Optional Holiday List not set for leave period {0},Optionale Feiertagsliste ist für Abwesenheitszeitraum {0} nicht festgelegt, +Optional Holiday List not set for leave period {0},Liste der optionalen Feiertage ist für Abwesenheitsperiode {0} nicht festgelegt, Part-time,Teilzeit, Password policy for Salary Slips is not set,Die Kennwortrichtlinie für Gehaltsabrechnungen ist nicht festgelegt, Payment Days,Zahlungsziel, @@ -196,9 +196,9 @@ Submit this to create the Employee record,"Übergeben Sie dies, um den Mitarbeit Submitting Salary Slips...,Lohnzettel einreichen ..., Team Updates,Team-Updates, Thank you,Danke, -The day(s) on which you are applying for leave are holidays. You need not apply for leave.,"Die Tage, für die Sie Urlaub beantragen, sind arbeitsfreie Tage. Deshalb müssen Sie keinen Urlaub beantragen.", +The day(s) on which you are applying for leave are holidays. You need not apply for leave.,"Die Tage, für die Sie Abwesenheit beantragen, sind arbeitsfreie Tage. Daher müssen Sie keine Abwesenheit beantragen.", There are more holidays than working days this month.,Es gibt mehr Feiertage als Arbeitstage in diesem Monat., -There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1}, +There is no leave period in between {0} and {1},Zwischen {0} und {1} wurde noch keine Abwesenheitsperiode konfiguriert, This is based on the attendance of this Employee,Dies hängt von der Anwesenheit dieses Mitarbeiters ab, This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?,Dies wird Gehaltsabrechnungen übermitteln und eine periodengerechte Journalbuchung erstellen. Willst du fortfahren?, To date can not be equal or less than from date,Bis heute kann nicht gleich oder weniger als von Datum sein, @@ -222,12 +222,12 @@ Update Response,Antwort aktualisieren, Value missing,Fehlender Wert, Variable,Variable, Walk In,Laufkundschaft, -Warning: Leave application contains following block dates,Achtung: Die Urlaubsverwaltung enthält die folgenden gesperrten Daten, +Warning: Leave application contains following block dates,Achtung: Der Abwesenheitsantrag enthält die folgenden gesperrten Daten, Website Listing,Website-Liste, Work Summary for {0},Arbeitszusammenfassung für {0}, -You are not authorized to approve leaves on Block Dates,"Sie sind nicht berechtigt, Urlaube für geblockte Termine zu genehmigen", +You are not authorized to approve leaves on Block Dates,"Sie sind nicht berechtigt, Abwesenheiten für geblockte Termine zu genehmigen", You are not present all day(s) between compensatory leave request days,Sie sind nicht den ganzen Tag (oder mehreren Tagen) zwischen den Ausgleichsurlaubsantragstagen anwesend, -You can only submit Leave Encashment for a valid encashment amount,Sie können die Einzahlung nur für einen gültigen Einlösungsbetrag einreichen, +You can only submit Leave Encashment for a valid encashment amount,Sie können nur Abwesenheitsauszahlung für einen gültigen Auszahlungsbetrag einreichen, {0} already allocated for Employee {1} for period {2} to {3},{0} bereits an Mitarbeiter {1} zugeteilt für den Zeitraum {2} bis {3}, {0} applicable after {1} working days,{0} gilt nach {1} Werktagen, {0} is not in Optional Holiday List,{0} befindet sich nicht in der optionalen Feiertagsliste, @@ -351,7 +351,7 @@ Department Approver,Abteilungsgenehmiger, Approver,Genehmiger, Designation Skill,Positions Fähigkeit, Skill,Fertigkeit, -Leave Policy,Urlaubsrichtlinie, +Leave Policy,Abwesenheitsrichtlinie, Salary Details,Gehaltsdetails, HR-EAD-.YYYY.-,HR-EAD-.YYYY.-, Claimed,Behauptet, @@ -510,7 +510,7 @@ Add unused leaves from previous allocations,Ungenutzte Abwesenheiten aus vorheri Unused leaves,Ungenutzte Abwesenheiten, Total Leaves Allocated,Insgesamt zugewiesene Abwesenheiten, Total Leaves Encashed,Insgesamt ausbezahlte Abwesenheiten, -Leave Period,Urlaubszeitraum, +Leave Period,Abwesenheitsperiode, Carry Forwarded Leaves,Übertragene Abwesenheitstage, Apply / Approve Leaves,Abwesenheiten beantragen/genehmigen, HR-LAP-.YYYY.-,HR-LAP-.YYYY.-, @@ -523,16 +523,16 @@ Leave Block List Name,Name der Abwesenheitssperrliste, Applies to Company,Gilt für Unternehmen, "If not checked, the list will have to be added to each Department where it has to be applied.","Wenn deaktiviert, muss die Liste zu jeder Abteilung, für die sie gelten soll, hinzugefügt werden.", Block Days,Tage sperren, -Stop users from making Leave Applications on following days.,"Benutzer davon abhalten, Urlaubsanträge für folgende Tage einzureichen.", +Stop users from making Leave Applications on following days.,"Benutzer davon abhalten, Abwesenheitsanträge für folgende Tage einzureichen.", Leave Block List Dates,Abwesenheitssperrliste Termine, Allow Users,Benutzer zulassen, -Allow the following users to approve Leave Applications for block days.,"Zulassen, dass die folgenden Benutzer Urlaubsanträge für Blöcke von Tagen genehmigen können.", +Allow the following users to approve Leave Applications for block days.,"Zulassen, dass die folgenden Benutzer Abwesenheitsanträge für blockierte Tagen genehmigen können.", Leave Block List Allowed,Abwesenheitssperrliste zugelassen, Leave Block List Allow,Abwesenheitssperrliste zulassen, Allow User,Benutzer zulassen, Leave Block List Date,Abwesenheitssperrliste Datum, Block Date,Datum sperren, -Leave Control Panel,Urlaubsverwaltung, +Leave Control Panel,Abwesenheitskontrollfeld, Employment Type (optional),Anstellungsart (optional), Branch (optional),Zweigstelle (optional), Department (optional),Abteilung (optional), @@ -553,8 +553,8 @@ Is Expired,Ist abgelaufen, Is Leave Without Pay,Ist unbezahlt, Holiday List for Optional Leave,Feiertagsliste für optionale Abwesenheit, Leave Allocations,Abwesenheitskontingente, -Leave Policy Details,Urlaubsrichtliniendetails, -Leave Policy Detail,Urlaubsrichtliniendetail, +Leave Policy Details,Abwesenheitsrichtliniendetails, +Leave Policy Detail,Abwesenheitsrichtliniendetail, Annual Allocation,Jährliche Zuteilung, Leave Type Name,Bezeichnung der Abwesenheit, Applicable After (Working Days),Anwendbar nach (Werktagen), From bfe7c5c29fd958a3748464d9fde7035102c77803 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 22:49:25 +0530 Subject: [PATCH 38/79] fix: company fixture setup error handling & flags (backport #1504) (#1506) * fix: company fixture setup error handling & flags * chore: fix linter * chore: fix linter * fix: ignore validations during custom field setup in installation - most of these are from unrelated changes in other doctype schemas, happens mostly when site is created from backup (cherry picked from commit 6ca8cab4cc5a2862604dff7b0cb5dd1fea64ed44) Co-authored-by: Rucha Mahabal --- hrms/overrides/company.py | 35 +++++++++++++-------- hrms/regional/india/setup.py | 9 +++--- hrms/regional/united_arab_emirates/setup.py | 4 +-- hrms/setup.py | 6 ++-- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/hrms/overrides/company.py b/hrms/overrides/company.py index fc10d0e1e8..59b31c98c6 100644 --- a/hrms/overrides/company.py +++ b/hrms/overrides/company.py @@ -31,13 +31,11 @@ def delete_company_fixtures(): except (ImportError, AttributeError): # regional file or method does not exist pass - except Exception: - frappe.log_error("Unable to delete country fixtures for HRMS") - frappe.throw( - _("Failed to delete defaults for country {0}. Please contact support.").format( - frappe.bold(country) - ) - ) + except Exception as e: + frappe.log_error("Unable to delete country fixtures for Frappe HR") + msg = _("Failed to delete defaults for country {0}.").format(frappe.bold(country)) + msg += "

" + _("{0}: {1}").format(frappe.bold(_("Error")), get_error_message(e)) + frappe.throw(msg, title=_("Country Fixture Deletion Failed")) def run_regional_setup(country): @@ -46,13 +44,24 @@ def run_regional_setup(country): frappe.get_attr(module_name)() except ImportError: pass + except Exception as e: + frappe.log_error("Unable to setup country fixtures for Frappe HR") + msg = _("Failed to setup defaults for country {0}.").format(frappe.bold(country)) + msg += "

" + _("{0}: {1}").format(frappe.bold(_("Error")), get_error_message(e)) + frappe.throw(msg, title=_("Country Setup failed")) + + +def get_error_message(error) -> str: + try: + message_log = frappe.message_log.pop() if frappe.message_log else str(error) + if isinstance(message_log, str): + error_message = json.loads(message_log).get("message") + else: + error_message = message_log.get("message") except Exception: - frappe.log_error("Unable to setup country fixtures for HRMS") - frappe.throw( - _("Failed to setup defaults for country {0}. Please contact support.").format( - frappe.bold(country) - ) - ) + error_message = message_log + + return error_message def make_salary_components(country): diff --git a/hrms/regional/india/setup.py b/hrms/regional/india/setup.py index ab315eb07d..e2ebbb55cc 100644 --- a/hrms/regional/india/setup.py +++ b/hrms/regional/india/setup.py @@ -240,13 +240,13 @@ def add_custom_roles_for_reports(): "Income Tax Deductions", ): if not frappe.db.get_value("Custom Role", dict(report=report_name)): - frappe.get_doc( + doc = frappe.new_doc("Custom Role") + doc.update( dict( - doctype="Custom Role", report=report_name, roles=[dict(role="HR User"), dict(role="HR Manager"), dict(role="Employee")], ) - ).insert() + ).insert(ignore_permissions=True) def create_gratuity_rule_for_india(): @@ -272,5 +272,4 @@ def create_gratuity_rule_for_india(): ], } ) - rule.flags.ignore_mandatory = True - rule.save() + rule.insert(ignore_permissions=True, ignore_mandatory=True) diff --git a/hrms/regional/united_arab_emirates/setup.py b/hrms/regional/united_arab_emirates/setup.py index 50e804c1be..2627cc3059 100644 --- a/hrms/regional/united_arab_emirates/setup.py +++ b/hrms/regional/united_arab_emirates/setup.py @@ -12,9 +12,7 @@ def create_gratuity_rules_for_uae(): docs = get_gratuity_rules() for d in docs: doc = frappe.get_doc(d) - doc.flags.ignore_mandatory = True - doc.flags.ignore_permissions = True - doc.insert(ignore_if_duplicate=True) + doc.insert(ignore_if_duplicate=True, ignore_permissions=True, ignore_mandatory=True) def get_gratuity_rules(): diff --git a/hrms/setup.py b/hrms/setup.py index 92cce6445a..930b6faf8f 100644 --- a/hrms/setup.py +++ b/hrms/setup.py @@ -12,7 +12,7 @@ def after_install(): - create_custom_fields(get_custom_fields()) + create_custom_fields(get_custom_fields(), ignore_validate=True) create_salary_slip_loan_fields() make_fixtures() setup_notifications() @@ -302,7 +302,7 @@ def get_custom_fields(): def create_salary_slip_loan_fields(): if "lending" in frappe.get_installed_apps(): - create_custom_fields(SALARY_SLIP_LOAN_FIELDS) + create_custom_fields(SALARY_SLIP_LOAN_FIELDS, ignore_validate=True) def after_app_install(app_name): @@ -311,7 +311,7 @@ def after_app_install(app_name): return print("Updating payroll setup for loans") - create_custom_fields(SALARY_SLIP_LOAN_FIELDS) + create_custom_fields(SALARY_SLIP_LOAN_FIELDS, ignore_validate=True) def before_app_uninstall(app_name): From 720c37edb789807b864210f0674aad3f7e238ef8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 Mar 2024 22:39:43 +0530 Subject: [PATCH 39/79] fix: handle FCM config fetch calls in case of incorrect relay server URL (cherry picked from commit e2b9fe0aa83435c93e4564f834749e007c5bc23a) --- frontend/public/frappe-push-notification.js | 32 ++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/frontend/public/frappe-push-notification.js b/frontend/public/frappe-push-notification.js index 37dcbe21e7..4b42f06f59 100644 --- a/frontend/public/frappe-push-notification.js +++ b/frontend/public/frappe-push-notification.js @@ -87,11 +87,17 @@ class FrappePushNotification { if (this.webConfig !== null && this.webConfig !== undefined) { return this.webConfig } - let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` - let response = await fetch(url) - let response_json = await response.json() - this.webConfig = response_json.config - return this.webConfig + try { + let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` + let response = await fetch(url) + let response_json = await response.json() + this.webConfig = response_json.config + return this.webConfig + } catch (e) { + throw new Error( + "Push Notification Relay is not configured properly on your site." + ) + } } /** @@ -103,11 +109,17 @@ class FrappePushNotification { if (this.vapidPublicKey !== "") { return this.vapidPublicKey } - let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` - let response = await fetch(url) - let response_json = await response.json() - this.vapidPublicKey = response_json.vapid_public_key - return this.vapidPublicKey + try { + let url = `${FrappePushNotification.relayServerBaseURL}/api/method/notification_relay.api.get_config?project_name=${this.projectName}` + let response = await fetch(url) + let response_json = await response.json() + this.vapidPublicKey = response_json.vapid_public_key + return this.vapidPublicKey + } catch (e) { + throw new Error( + "Push Notification Relay is not configured properly on your site." + ) + } } /** From a04b5daaa941940745b682fe055658b75fed5cba Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 Mar 2024 22:40:26 +0530 Subject: [PATCH 40/79] refactor: handle FCM initialization errors in service worker (cherry picked from commit 665128d0768b8764ac6b2d86d0e39dafc1400b83) --- frontend/public/sw.js | 73 ++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 038c1a19d5..cb110afeb7 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -11,47 +11,54 @@ precacheAndRoute(self.__WB_MANIFEST) cleanupOutdatedCaches() const jsonConfig = new URL(location).searchParams.get("config") -const firebaseApp = initializeApp(JSON.parse(jsonConfig)) -const messaging = getMessaging(firebaseApp) -function isChrome() { - return navigator.userAgent.toLowerCase().includes("chrome") -} +// Firebase config initialization +try { + const firebaseApp = initializeApp(JSON.parse(jsonConfig)) + const messaging = getMessaging(firebaseApp) -onBackgroundMessage(messaging, (payload) => { - const notificationTitle = payload.data.title - let notificationOptions = { - body: payload.data.body || "", - } - if (payload.data.notification_icon) { - notificationOptions["icon"] = payload.data.notification_icon + function isChrome() { + return navigator.userAgent.toLowerCase().includes("chrome") } - if (isChrome()) { - notificationOptions["data"] = { - url: payload.data.click_action, + + onBackgroundMessage(messaging, (payload) => { + const notificationTitle = payload.data.title + let notificationOptions = { + body: payload.data.body || "", } - } else { - if (payload.data.click_action) { - notificationOptions["actions"] = [ - { - action: payload.data.click_action, - title: "View Details", - }, - ] + if (payload.data.notification_icon) { + notificationOptions["icon"] = payload.data.notification_icon } - } - self.registration.showNotification(notificationTitle, notificationOptions) -}) - -if (isChrome()) { - self.addEventListener("notificationclick", (event) => { - event.stopImmediatePropagation() - event.notification.close() - if (event.notification.data && event.notification.data.url) { - clients.openWindow(event.notification.data.url) + if (isChrome()) { + notificationOptions["data"] = { + url: payload.data.click_action, + } + } else { + if (payload.data.click_action) { + notificationOptions["actions"] = [ + { + action: payload.data.click_action, + title: "View Details", + }, + ] + } } + self.registration.showNotification(notificationTitle, notificationOptions) }) + + if (isChrome()) { + self.addEventListener("notificationclick", (event) => { + event.stopImmediatePropagation() + event.notification.close() + if (event.notification.data && event.notification.data.url) { + clients.openWindow(event.notification.data.url) + } + }) + } +} catch (error) { + console.log("Failed to initialize Firebase", error) } self.skipWaiting() clientsClaim() +console.log("Service Worker Initialized") From f804d197277a32e038aff9361c218b4871f8fc83 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 Mar 2024 22:41:00 +0530 Subject: [PATCH 41/79] fix: register service worker even if FCM initialization fails (cherry picked from commit 1846d24c3455435ed1a6e065524e82f841d0f57e) --- frontend/src/main.js | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/frontend/src/main.js b/frontend/src/main.js index 6172455f32..3eb61803d0 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -56,28 +56,38 @@ app.provide("$employee", employeeResource) app.provide("$socket", socket) app.provide("$dayjs", dayjs) -const registerServiceWorker = () => { +const registerServiceWorker = async () => { window.frappePushNotification = new FrappePushNotification("hrms") if ("serviceWorker" in navigator) { - window.frappePushNotification - .appendConfigToServiceWorkerURL("/assets/hrms/frontend/sw.js") - .then((url) => { - navigator.serviceWorker - .register(url, { - type: "classic", - }) - .then((registration) => { - window.frappePushNotification.initialize(registration).then(() => { - console.log("Frappe Push Notification initialized") - }) + let serviceWorkerURL = "/assets/hrms/frontend/sw.js" + let config = "" + + try { + config = await window.frappePushNotification.fetchWebConfig() + serviceWorkerURL = `${serviceWorkerURL}?config=${encodeURIComponent( + JSON.stringify(config) + )}` + } catch (err) { + console.error("Failed to fetch FCM config", err) + } + + navigator.serviceWorker + .register(serviceWorkerURL, { + type: "classic", + }) + .then((registration) => { + if (config) { + window.frappePushNotification.initialize(registration).then(() => { + console.log("Frappe Push Notification initialized") }) + } }) .catch((err) => { console.error("Failed to register service worker", err) }) } else { - console.error("Service worker not enabled/supported by browser") + console.error("Service worker not enabled/supported by the browser") } } From 4fb352d8cd992f389ee23c453d420fd90b0e4bc7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 12:44:07 +0530 Subject: [PATCH 42/79] refactor: replace `in_list(array, member)` with `array.includes(member)` (backport #1513) (#1515) (cherry picked from commit dbf4f1c49e6587a8c06176723e5de06d3a2c569d) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- hrms/public/js/erpnext/journal_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hrms/public/js/erpnext/journal_entry.js b/hrms/public/js/erpnext/journal_entry.js index b2e889f2f8..39984096ad 100644 --- a/hrms/public/js/erpnext/journal_entry.js +++ b/hrms/public/js/erpnext/journal_entry.js @@ -49,7 +49,7 @@ frappe.ui.form.on("Journal Entry", { ] }; - if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) { + if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) { out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]); // Filter by cost center if (jvd.cost_center) { @@ -61,7 +61,7 @@ frappe.ui.form.on("Journal Entry", { out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]); } - if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) { + if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) { // party_type and party mandatory frappe.model.validate_missing(jvd, "party_type"); frappe.model.validate_missing(jvd, "party"); From ac9030ca152c8b3c549135479cc3b1059d554e4c Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 25 Jan 2024 14:42:26 +0530 Subject: [PATCH 43/79] feat: create Bulk Salary Structure Assignment doctype (cherry picked from commit 8076b164741be0076c28437c9256b36d09233a5e) --- .../__init__.py | 0 .../bulk_salary_structure_assignment.js | 8 + .../bulk_salary_structure_assignment.json | 164 ++++++++++++++++++ .../bulk_salary_structure_assignment.py | 9 + .../test_bulk_salary_structure_assignment.py | 9 + 5 files changed, 190 insertions(+) create mode 100644 hrms/payroll/doctype/bulk_salary_structure_assignment/__init__.py create mode 100644 hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js create mode 100644 hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json create mode 100644 hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py create mode 100644 hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/__init__.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js new file mode 100644 index 0000000000..bc6cc00b04 --- /dev/null +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Bulk Salary Structure Assignment", { +// refresh(frm) { + +// }, +// }); diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json new file mode 100644 index 0000000000..26f71e9ffa --- /dev/null +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json @@ -0,0 +1,164 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-01-25 12:52:26.250137", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "set_assignment_details_section", + "salary_structure", + "from_date", + "income_tax_slab", + "column_break_rsep", + "payroll_payable_account", + "base_variable_section", + "base", + "column_break_zsgt", + "variable", + "section_break_snxy", + "branch", + "department", + "column_break_jcpq", + "designation", + "employee_grade", + "advanced_filters_section", + "filter_list", + "select_employees_section", + "employees_html" + ], + "fields": [ + { + "fieldname": "set_assignment_details_section", + "fieldtype": "Section Break", + "label": "Set Assignment Details" + }, + { + "fieldname": "salary_structure", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Salary Structure", + "options": "Salary Structure", + "reqd": 1 + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From Date", + "reqd": 1 + }, + { + "fieldname": "income_tax_slab", + "fieldtype": "Link", + "label": "Income Tax Slab", + "options": "Income Tax Slab" + }, + { + "fieldname": "column_break_rsep", + "fieldtype": "Column Break" + }, + { + "fieldname": "payroll_payable_account", + "fieldtype": "Link", + "label": "Payroll Payable Account", + "options": "Account" + }, + { + "fieldname": "base_variable_section", + "fieldtype": "Section Break", + "label": "Base & Variable" + }, + { + "fieldname": "base", + "fieldtype": "Currency", + "label": "Base" + }, + { + "fieldname": "column_break_zsgt", + "fieldtype": "Column Break" + }, + { + "fieldname": "variable", + "fieldtype": "Currency", + "label": "Variable" + }, + { + "collapsible": 1, + "fieldname": "section_break_snxy", + "fieldtype": "Section Break", + "label": "Quick Filters" + }, + { + "fieldname": "branch", + "fieldtype": "Link", + "label": "Branch", + "options": "Branch" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department" + }, + { + "fieldname": "column_break_jcpq", + "fieldtype": "Column Break" + }, + { + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" + }, + { + "fieldname": "employee_grade", + "fieldtype": "Link", + "label": "Employee Grade", + "options": "Employee Grade" + }, + { + "collapsible": 1, + "fieldname": "advanced_filters_section", + "fieldtype": "Section Break", + "label": "Advanced Filters" + }, + { + "fieldname": "filter_list", + "fieldtype": "HTML", + "label": "Filter List" + }, + { + "fieldname": "select_employees_section", + "fieldtype": "Section Break", + "label": "Select Employees" + }, + { + "fieldname": "employees_html", + "fieldtype": "HTML", + "label": "Employees HTML" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2024-01-25 13:48:28.502117", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Bulk Salary Structure Assignment", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py new file mode 100644 index 0000000000..320cbd2a73 --- /dev/null +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class BulkSalaryStructureAssignment(Document): + pass diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py new file mode 100644 index 0000000000..679674ff5e --- /dev/null +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBulkSalaryStructureAssignment(FrappeTestCase): + pass From 105a97d1eb94402275f77c2833e2971138e57d7d Mon Sep 17 00:00:00 2001 From: krantheman Date: Mon, 29 Jan 2024 16:10:23 +0530 Subject: [PATCH 44/79] feat: add Quick and Advanced Filters (cherry picked from commit 2e77bba5bc2bbd8c03213c06c401edda68a6cf43) --- .../bulk_salary_structure_assignment.js | 158 +++++++++++++++++- .../bulk_salary_structure_assignment.json | 44 +++-- .../bulk_salary_structure_assignment.py | 35 +++- 3 files changed, 216 insertions(+), 21 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index bc6cc00b04..45c8c9dbbd 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -1,8 +1,158 @@ // Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("Bulk Salary Structure Assignment", { -// refresh(frm) { +frappe.ui.form.on("Bulk Salary Structure Assignment", { + setup: function (frm) { + frm.trigger("set_query"); + frappe.model.with_doctype("Employee", () => + frm.trigger("setup_filter_group") + ); + }, -// }, -// }); + refresh(frm) { + frm.trigger("get_employees"); + }, + + from_date(frm) { + frm.trigger("get_employees"); + }, + + company(frm) { + frm.trigger("get_employees"); + }, + + branch(frm) { + frm.trigger("get_employees"); + }, + + department(frm) { + frm.trigger("get_employees"); + }, + + employment_type(frm) { + frm.trigger("get_employees"); + }, + + designation(frm) { + frm.trigger("get_employees"); + }, + + grade(frm) { + frm.trigger("get_employees"); + }, + + set_query(frm) { + frm.set_query("payroll_payable_account", function () { + return { + filters: { + root_type: "Liability", + is_group: 0, + }, + }; + }); + }, + + setup_filter_group(frm) { + const filter_wrapper = frm.fields_dict.filter_list.$wrapper; + filter_wrapper.empty(); + + frm.filter_list = new frappe.ui.FilterGroup({ + parent: filter_wrapper, + doctype: "Employee", + on_change: () => { + frm.advanced_filters = frm.filter_list + .get_filters() + .reduce((filters, item) => { + // item[3] is the value from the array [doctype, fieldname, condition, value] + if (item[3]) { + filters.push(item.slice(1, 4)); + } + return filters; + }, []); + frm.trigger("get_employees"); + }, + }); + }, + + get_employees(frm) { + frm + .call({ + method: "get_employees", + args: { + advanced_filters: frm.advanced_filters || [], + }, + doc: frm.doc, + }) + .then((r) => { + // section automatically collapses on applying a single filter + frm.set_df_property("quick_filters_section", "collapsible", 0); + frm.set_df_property("advanced_filters_section", "collapsible", 0); + + frm.employees = r.message; + frm.events.render_employees_table(frm); + }); + }, + + render_employees_table(frm) { + const columns = frm.events.get_columns_for_employees_table(); + + if (frm.employees_datatable) { + frm.employees_datatable.rowmanager.checkMap = []; + frm.employees_datatable.refresh(frm.employees, columns); + return; + } + + const $wrapper = frm.get_field("employees_html").$wrapper; + frm.employee_wrapper = $(`
`).appendTo( + $wrapper + ); + const datatable_options = { + columns: columns, + data: frm.employees, + checkboxColumn: true, + checkedRowStatus: false, + serialNoColumn: false, + dynamicRowHeight: true, + inlineFilters: true, + layout: "fluid", + cellHeight: 35, + noDataMessage: __("No Data"), + disableReorderColumn: true, + }; + frm.employees_datatable = new frappe.DataTable( + frm.employee_wrapper.get(0), + datatable_options + ); + }, + + get_columns_for_employees_table() { + return [ + { + name: "employee", + id: "employee", + content: __("Employee"), + }, + { + name: "employee_name", + id: "employee_name", + content: __("Name"), + }, + { + name: "company", + id: "company", + content: __("Company"), + }, + { + name: "department", + id: "department", + content: __("Department"), + }, + ].map((x) => ({ + ...x, + editable: false, + focusable: false, + dropdown: false, + align: "left", + })); + }, +}); diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json index 26f71e9ffa..1f3ce23c61 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json @@ -15,12 +15,14 @@ "base", "column_break_zsgt", "variable", - "section_break_snxy", + "quick_filters_section", + "company", "branch", "department", "column_break_jcpq", + "employment_type", "designation", - "employee_grade", + "grade", "advanced_filters_section", "filter_list", "select_employees_section", @@ -82,12 +84,6 @@ "fieldtype": "Currency", "label": "Variable" }, - { - "collapsible": 1, - "fieldname": "section_break_snxy", - "fieldtype": "Section Break", - "label": "Quick Filters" - }, { "fieldname": "branch", "fieldtype": "Link", @@ -110,12 +106,6 @@ "label": "Designation", "options": "Designation" }, - { - "fieldname": "employee_grade", - "fieldtype": "Link", - "label": "Employee Grade", - "options": "Employee Grade" - }, { "collapsible": 1, "fieldname": "advanced_filters_section", @@ -136,12 +126,36 @@ "fieldname": "employees_html", "fieldtype": "HTML", "label": "Employees HTML" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "fieldname": "employment_type", + "fieldtype": "Link", + "label": "Employment Type", + "options": "Employment Type" + }, + { + "fieldname": "grade", + "fieldtype": "Link", + "label": "Employee Grade", + "options": "Employee Grade" + }, + { + "collapsible": 1, + "fieldname": "quick_filters_section", + "fieldtype": "Section Break", + "label": "Quick Filters" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-01-25 13:48:28.502117", + "modified": "2024-01-29 16:05:23.083034", "modified_by": "Administrator", "module": "Payroll", "name": "Bulk Salary Structure Assignment", diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 320cbd2a73..99b46f7b8b 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -1,9 +1,40 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from frappe.query_builder.terms import SubQuery class BulkSalaryStructureAssignment(Document): - pass + @frappe.whitelist() + def get_employees(self, advanced_filters: list) -> list: + Assignment = frappe.qb.DocType("Salary Structure Assignment") + employees_with_assignments = ( + frappe.qb.from_(Assignment) + .select(Assignment.employee) + .distinct() + .where((Assignment.from_date == self.from_date) & (Assignment.docstatus == 1)) + .run(pluck=True) + ) + + quick_filter_fields = [ + "company", + "employment_type", + "branch", + "department", + "designation", + "grade", + ] + quick_filters = [[d, "=", self.get(d)] for d in quick_filter_fields if self.get(d)] + + filters = ( + [["status", "=", "Active"], ["employee", "not in", employees_with_assignments]] + + quick_filters + + advanced_filters + ) + return frappe.get_list( + "Employee", + filters=filters, + fields=["employee", "employee_name", "company", "department"], + ) From 25225750235391023c6b93913a622f3ed557b496 Mon Sep 17 00:00:00 2001 From: krantheman Date: Mon, 29 Jan 2024 18:41:50 +0530 Subject: [PATCH 45/79] refactor: move Base & Variable to datatable (cherry picked from commit 596f2356c56ead1331af719f749515fdeb3ebf84) --- .../bulk_salary_structure_assignment.js | 112 +++++++++++++----- .../bulk_salary_structure_assignment.json | 45 +++---- .../bulk_salary_structure_assignment.py | 3 +- 3 files changed, 99 insertions(+), 61 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 45c8c9dbbd..a0afeb49ad 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -2,14 +2,14 @@ // For license information, please see license.txt frappe.ui.form.on("Bulk Salary Structure Assignment", { - setup: function (frm) { + setup(frm) { frm.trigger("set_query"); - frappe.model.with_doctype("Employee", () => - frm.trigger("setup_filter_group") - ); + frm.trigger("setup_filter_group"); }, - refresh(frm) { + async refresh(frm) { + frm.trigger("set_primary_action"); + await frm.trigger("set_payroll_payable_account"); frm.trigger("get_employees"); }, @@ -17,7 +17,8 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.trigger("get_employees"); }, - company(frm) { + async company(frm) { + await frm.trigger("set_payroll_payable_account"); frm.trigger("get_employees"); }, @@ -41,36 +42,83 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.trigger("get_employees"); }, + set_primary_action(frm) { + frm.disable_save(); + frm.page.set_primary_action(__("Assign Structure"), () => { + console.log("Assigning"); + }); + }, + set_query(frm) { + frm.set_query("salary_structure", function () { + return { + filters: { + company: frm.doc.company, + is_active: "Yes", + docstatus: 1, + }, + }; + }); + frm.set_query("income_tax_slab", function () { + return { + filters: { + company: frm.doc.company, + disabled: 0, + docstatus: 1, + currency: frm.doc.currency, + }, + }; + }); frm.set_query("payroll_payable_account", function () { + const company_currency = erpnext.get_currency(frm.doc.company); return { filters: { + company: frm.doc.company, root_type: "Liability", is_group: 0, + account_currency: ["in", [frm.doc.currency, company_currency]], }, }; }); }, + set_payroll_payable_account(frm) { + if (frm.doc.company) { + frappe.db.get_value( + "Company", + frm.doc.company, + "default_payroll_payable_account", + (r) => { + frm.set_value( + "payroll_payable_account", + r.default_payroll_payable_account + ); + } + ); + } + }, + setup_filter_group(frm) { const filter_wrapper = frm.fields_dict.filter_list.$wrapper; filter_wrapper.empty(); - frm.filter_list = new frappe.ui.FilterGroup({ - parent: filter_wrapper, - doctype: "Employee", - on_change: () => { - frm.advanced_filters = frm.filter_list - .get_filters() - .reduce((filters, item) => { - // item[3] is the value from the array [doctype, fieldname, condition, value] - if (item[3]) { - filters.push(item.slice(1, 4)); - } - return filters; - }, []); - frm.trigger("get_employees"); - }, + frappe.model.with_doctype("Employee", () => { + frm.filter_list = new frappe.ui.FilterGroup({ + parent: filter_wrapper, + doctype: "Employee", + on_change: () => { + frm.advanced_filters = frm.filter_list + .get_filters() + .reduce((filters, item) => { + // item[3] is the value from the array [doctype, fieldname, condition, value] + if (item[3]) { + filters.push(item.slice(1, 4)); + } + return filters; + }, []); + frm.trigger("get_employees"); + }, + }); }); }, @@ -94,7 +142,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, render_employees_table(frm) { - const columns = frm.events.get_columns_for_employees_table(); + const columns = frm.events.columns_for_employees_table(); if (frm.employees_datatable) { frm.employees_datatable.rowmanager.checkMap = []; @@ -125,32 +173,34 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, - get_columns_for_employees_table() { + columns_for_employees_table() { return [ { name: "employee", id: "employee", content: __("Employee"), + editable: false, + focusable: false, }, { name: "employee_name", id: "employee_name", content: __("Name"), + editable: false, + focusable: false, }, { - name: "company", - id: "company", - content: __("Company"), + name: "base", + id: "base", + content: __("Base"), }, { - name: "department", - id: "department", - content: __("Department"), + name: "variable", + id: "variable", + content: __("Variable"), }, ].map((x) => ({ ...x, - editable: false, - focusable: false, dropdown: false, align: "left", })); diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json index 1f3ce23c61..7768918042 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json @@ -10,19 +10,16 @@ "from_date", "income_tax_slab", "column_break_rsep", + "company", "payroll_payable_account", - "base_variable_section", - "base", - "column_break_zsgt", - "variable", + "currency", "quick_filters_section", - "company", "branch", "department", - "column_break_jcpq", - "employment_type", "designation", + "column_break_jcpq", "grade", + "employment_type", "advanced_filters_section", "filter_list", "select_employees_section", @@ -60,30 +57,12 @@ "fieldtype": "Column Break" }, { + "fetch_from": ".default_payroll_payable_account", "fieldname": "payroll_payable_account", "fieldtype": "Link", "label": "Payroll Payable Account", "options": "Account" }, - { - "fieldname": "base_variable_section", - "fieldtype": "Section Break", - "label": "Base & Variable" - }, - { - "fieldname": "base", - "fieldtype": "Currency", - "label": "Base" - }, - { - "fieldname": "column_break_zsgt", - "fieldtype": "Column Break" - }, - { - "fieldname": "variable", - "fieldtype": "Currency", - "label": "Variable" - }, { "fieldname": "branch", "fieldtype": "Link", @@ -131,7 +110,8 @@ "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company" + "options": "Company", + "reqd": 1 }, { "fieldname": "employment_type", @@ -150,12 +130,21 @@ "fieldname": "quick_filters_section", "fieldtype": "Section Break", "label": "Quick Filters" + }, + { + "depends_on": "salary_structure", + "fetch_from": "salary_structure.currency", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-01-29 16:05:23.083034", + "modified": "2024-01-29 18:19:29.013027", "modified_by": "Administrator", "module": "Payroll", "name": "Bulk Salary Structure Assignment", diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 99b46f7b8b..fc699ab7d6 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -3,7 +3,6 @@ import frappe from frappe.model.document import Document -from frappe.query_builder.terms import SubQuery class BulkSalaryStructureAssignment(Document): @@ -36,5 +35,5 @@ def get_employees(self, advanced_filters: list) -> list: return frappe.get_list( "Employee", filters=filters, - fields=["employee", "employee_name", "company", "department"], + fields=["employee", "employee_name"], ) From 2405f2f0aa8185df590476f798e4e2af894b5c2d Mon Sep 17 00:00:00 2001 From: krantheman Date: Tue, 30 Jan 2024 12:42:30 +0530 Subject: [PATCH 46/79] refactor: redirect Bulk Assignments in Salary Structure to Bulk Salary Structure Assignment (cherry picked from commit 946cb061d9efd1bb0e9082b54b253be34033ddf8) --- .../bulk_salary_structure_assignment.js | 26 ++++----- .../bulk_salary_structure_assignment.json | 3 +- .../salary_structure/salary_structure.js | 55 ++----------------- 3 files changed, 18 insertions(+), 66 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index a0afeb49ad..3cda667df3 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -83,19 +83,17 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, set_payroll_payable_account(frm) { - if (frm.doc.company) { - frappe.db.get_value( - "Company", - frm.doc.company, - "default_payroll_payable_account", - (r) => { - frm.set_value( - "payroll_payable_account", - r.default_payroll_payable_account - ); - } - ); - } + frappe.db.get_value( + "Company", + frm.doc.company, + "default_payroll_payable_account", + (r) => { + frm.set_value( + "payroll_payable_account", + r.default_payroll_payable_account + ); + } + ); }, setup_filter_group(frm) { @@ -136,7 +134,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.set_df_property("quick_filters_section", "collapsible", 0); frm.set_df_property("advanced_filters_section", "collapsible", 0); - frm.employees = r.message; + frm.employees = r.message.map((d) => ({ base: 0, variable: 0, ...d })); frm.events.render_employees_table(frm); }); }, diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json index 7768918042..89ddd4c163 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json @@ -47,6 +47,7 @@ "reqd": 1 }, { + "depends_on": "salary_structure", "fieldname": "income_tax_slab", "fieldtype": "Link", "label": "Income Tax Slab", @@ -144,7 +145,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-01-29 18:19:29.013027", + "modified": "2024-01-29 18:47:02.551273", "modified_by": "Administrator", "module": "Payroll", "name": "Bulk Salary Structure Assignment", diff --git a/hrms/payroll/doctype/salary_structure/salary_structure.js b/hrms/payroll/doctype/salary_structure/salary_structure.js index 1ac3fa9b72..7ede44cec0 100755 --- a/hrms/payroll/doctype/salary_structure/salary_structure.js +++ b/hrms/payroll/doctype/salary_structure/salary_structure.js @@ -104,7 +104,10 @@ frappe.ui.form.on("Salary Structure", { }, __("Create")); frm.add_custom_button(__("Bulk Assignments"), () => { - frm.trigger("assign_to_employees") + const doc = frappe.model.get_new_doc("Bulk Salary Structure Assignment"); + doc.salary_structure = frm.doc.name; + doc.company = frm.doc.company; + frappe.set_route("Form", "Bulk Salary Structure Assignment", doc.name); }, __("Create")) frm.add_custom_button(__("Income Tax Slab"), () => { @@ -134,56 +137,6 @@ frappe.ui.form.on("Salary Structure", { frm.trigger('set_earning_deduction_component'); }, - assign_to_employees:function (frm) { - var d = new frappe.ui.Dialog({ - title: __("Bulk Salary Structure Assignment"), - fields: [ - {fieldname: "sec_break", fieldtype: "Section Break", label: __("Employee Filters")}, - {fieldname: "branch", fieldtype: "Link", options: "Branch", label: __("Branch")}, - {fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')}, - {fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')}, - {fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")}, - {fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")}, - {fieldname:"payroll_payable_account", fieldtype: "Link", options: "Account", filters: {"company": frm.doc.company, "root_type": "Liability", "is_group": 0, "account_currency": frm.doc.currency}, label: __("Payroll Payable Account")}, - {fieldname:'base_variable', fieldtype:'Section Break'}, - {fieldname:'from_date', fieldtype:'Date', label: __('From Date'), "reqd": 1}, - {fieldname:'income_tax_slab', fieldtype:'Link', label: __('Income Tax Slab'), options: 'Income Tax Slab'}, - {fieldname:'base_col_br', fieldtype:'Column Break'}, - {fieldname:'base', fieldtype:'Currency', label: __('Base')}, - {fieldname:'variable', fieldtype:'Currency', label: __('Variable')} - ], - primary_action: function() { - var data = d.get_values(); - delete data.company - delete data.currency - frappe.call({ - doc: frm.doc, - method: "assign_salary_structure", - args: data, - callback: function(r) { - if(!r.exc) { - d.hide(); - frm.reload_doc(); - } - } - }); - }, - primary_action_label: __('Assign') - }); - - d.fields_dict.grade.df.onchange = function() { - const grade = d.fields_dict.grade.value; - if (grade) { - frappe.db.get_value('Employee Grade', grade, 'default_base_pay') - .then(({ message }) => { - d.set_value('base', message.default_base_pay); - }); - } - }; - - d.show(); - }, - salary_slip_based_on_timesheet: function(frm) { frm.trigger("toggle_fields"); hrms.set_payroll_frequency_to_null(frm); From 22c5408c0af2544f56fc6801c962ad4bbbd81583 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 31 Jan 2024 18:29:04 +0530 Subject: [PATCH 47/79] feat: add Bulk Assign server side functionality (cherry picked from commit b0f722a456896289d1d239ce7aead16a2cf01e6b) --- .../bulk_salary_structure_assignment.js | 25 ++++- .../bulk_salary_structure_assignment.py | 52 +++++++++ .../salary_structure/salary_structure.py | 101 +++--------------- .../salary_structure/test_salary_structure.py | 21 +++- 4 files changed, 106 insertions(+), 93 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 3cda667df3..7023ecc08c 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -45,7 +45,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { set_primary_action(frm) { frm.disable_save(); frm.page.set_primary_action(__("Assign Structure"), () => { - console.log("Assigning"); + frm.trigger("assign_structure"); }); }, @@ -203,4 +203,27 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { align: "left", })); }, + + assign_structure(frm) { + const check_map = frm.employees_datatable.rowmanager.checkMap; + const selected_employees = []; + check_map.forEach((is_checked, idx) => { + if (is_checked) + selected_employees.push(frm.employees_datatable.datamanager.data[idx]); + }); + frm + .call({ + method: "bulk_assign_structure", + doc: frm.doc, + args: { + employees: selected_employees, + }, + freeze: true, + freeze_message: __("Assigning Salary Structure"), + }) + .then((r) => { + // refresh only on complete/partial success + if (r.message.success) frm.refresh(); + }); + }, }); diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index fc699ab7d6..33b0b01f97 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -2,8 +2,13 @@ # For license information, please see license.txt import frappe +from frappe import _ from frappe.model.document import Document +from hrms.payroll.doctype.salary_structure.salary_structure import ( + create_salary_structure_assignment, +) + class BulkSalaryStructureAssignment(Document): @frappe.whitelist() @@ -37,3 +42,50 @@ def get_employees(self, advanced_filters: list) -> list: filters=filters, fields=["employee", "employee_name"], ) + + def validate_fields(self, employees: list): + for d in ["salary_structure", "from_date", "company"]: + if not self.get(d): + frappe.throw(_("{0} is required").format(self.meta.get_label(d)), title=_("Missing Field")) + if not employees: + frappe.throw( + _("Please select the employees to whom the salary structure should be assigned"), + title=_("No Employees Selected"), + ) + + @frappe.whitelist() + def bulk_assign_structure(self, employees: list): + self.validate_fields(employees) + + def _bulk_assign_structure(): + success, failure = [], [] + count = 0 + for d in employees: + savepoint = "before_allocation_submission" + frappe.db.savepoint(savepoint) + try: + create_salary_structure_assignment( + employee=d["employee"], + salary_structure=self.salary_structure, + company=self.company, + currency=self.currency, + payroll_payable_account=self.payroll_payable_account, + from_date=self.from_date, + base=d["base"], + variable=d["variable"], + income_tax_slab=self.income_tax_slab, + ) + success.append(d["employee"]) + count += 1 + frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) + + except Exception: + frappe.db.rollback(save_point=savepoint) + failure.append(d["employee"]) + + return {"success": success, "failure": failure} + + if len(employees) <= 20: + return _bulk_assign_structure() + + return frappe.enqueue(_bulk_assign_structure, timeout=600) diff --git a/hrms/payroll/doctype/salary_structure/salary_structure.py b/hrms/payroll/doctype/salary_structure/salary_structure.py index cbbfc4dba6..8b69d75ec4 100644 --- a/hrms/payroll/doctype/salary_structure/salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/salary_structure.py @@ -188,120 +188,43 @@ def get_employees(self, **kwargs): return employees - @frappe.whitelist() - def assign_salary_structure( - self, - branch=None, - grade=None, - department=None, - designation=None, - employee=None, - payroll_payable_account=None, - from_date=None, - base=None, - variable=None, - income_tax_slab=None, - ): - employees = self.get_employees( - company=self.company, - grade=grade, - department=department, - designation=designation, - name=employee, - branch=branch, - ) - - if employees: - if len(employees) > 20: - frappe.enqueue( - assign_salary_structure_for_employees, - timeout=600, - employees=employees, - salary_structure=self, - payroll_payable_account=payroll_payable_account, - from_date=from_date, - base=base, - variable=variable, - income_tax_slab=income_tax_slab, - ) - else: - assign_salary_structure_for_employees( - employees, - self, - payroll_payable_account=payroll_payable_account, - from_date=from_date, - base=base, - variable=variable, - income_tax_slab=income_tax_slab, - ) - else: - frappe.msgprint(_("No Employee Found")) - -def assign_salary_structure_for_employees( - employees, +def create_salary_structure_assignment( + employee, salary_structure, + company, + currency, + from_date, payroll_payable_account=None, - from_date=None, base=None, variable=None, income_tax_slab=None, -): - salary_structures_assignments = [] - existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date) - count = 0 - for employee in employees: - if employee in existing_assignments_for: - continue - count += 1 - - salary_structures_assignment = create_salary_structures_assignment( - employee, salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab - ) - salary_structures_assignments.append(salary_structures_assignment) - frappe.publish_progress( - count * 100 / len(set(employees) - set(existing_assignments_for)), - title=_("Assigning Structures..."), - ) - - if salary_structures_assignments: - frappe.msgprint(_("Structures have been assigned successfully")) - - -def create_salary_structures_assignment( - employee, - salary_structure, - payroll_payable_account, - from_date, - base, - variable, - income_tax_slab=None, ): if not payroll_payable_account: payroll_payable_account = frappe.db.get_value( - "Company", salary_structure.company, "default_payroll_payable_account" + "Company", company, "default_payroll_payable_account" ) if not payroll_payable_account: frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults')) payroll_payable_account_currency = frappe.db.get_value( "Account", payroll_payable_account, "account_currency" ) - company_curency = erpnext.get_company_currency(salary_structure.company) + company_curency = erpnext.get_company_currency(company) if ( - payroll_payable_account_currency != salary_structure.currency + payroll_payable_account_currency != currency and payroll_payable_account_currency != company_curency ): frappe.throw( _("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format( - salary_structure.currency, company_curency + currency, company_curency ) ) assignment = frappe.new_doc("Salary Structure Assignment") assignment.employee = employee - assignment.salary_structure = salary_structure.name - assignment.company = salary_structure.company - assignment.currency = salary_structure.currency + assignment.salary_structure = salary_structure + assignment.company = company + assignment.currency = currency assignment.payroll_payable_account = payroll_payable_account assignment.from_date = from_date assignment.base = base diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py index 1a7e1b50eb..b325237c73 100644 --- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py @@ -18,6 +18,9 @@ make_earning_salary_component, make_employee_salary_slip, ) +from hrms.payroll.doctype.salary_structure.salary_structure import ( + create_salary_structure_assignment as create_assignment, +) from hrms.payroll.doctype.salary_structure.salary_structure import make_salary_slip test_dependencies = ["Fiscal Year"] @@ -122,8 +125,14 @@ def test_salary_structures_assignment(self): ("test_assign_stucture@salary.com", salary_structure.name), ) # test structure_assignment - salary_structure.assign_salary_structure( - employee=employee_doc_name, from_date="2013-01-01", base=5000, variable=200 + create_assignment( + employee=employee_doc_name, + salary_structure=salary_structure.name, + from_date="2013-01-01", + company=salary_structure.company, + currency=salary_structure.currency, + base=5000, + variable=200, ) salary_structure_assignment = frappe.get_doc( "Salary Structure Assignment", {"employee": employee_doc_name, "from_date": "2013-01-01"} @@ -140,7 +149,13 @@ def test_employee_grade_defaults(self): employee = make_employee("test_employee_grade@salary.com", company="_Test Company", grade="Lead") # structure assignment should have the default salary structure and base pay - salary_structure.assign_salary_structure(employee=employee, from_date=nowdate()) + create_assignment( + employee=employee, + salary_structure=salary_structure.name, + company=salary_structure.company, + currency=salary_structure.currency, + from_date=nowdate(), + ) structure, base = frappe.db.get_value( "Salary Structure Assignment", {"employee": employee, "salary_structure": salary_structure.name, "from_date": nowdate()}, From 37ece3878e5aeebf5aa7c62a119b3a70c97c0f5a Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 1 Feb 2024 16:39:52 +0530 Subject: [PATCH 48/79] feat: status notification (cherry picked from commit af596ea4f201d60343a2630a9227c98c51b4999c) --- .../leave_control_panel.py | 48 ++------------ hrms/hr/utils.py | 40 ++++++++++++ .../bulk_salary_structure_assignment.json | 7 ++- .../bulk_salary_structure_assignment.py | 41 ++++++------ .../salary_structure/salary_structure.py | 63 ++++++++++--------- 5 files changed, 106 insertions(+), 93 deletions(-) diff --git a/hrms/hr/doctype/leave_control_panel/leave_control_panel.py b/hrms/hr/doctype/leave_control_panel/leave_control_panel.py index 8202078b6e..d2e66a1b87 100644 --- a/hrms/hr/doctype/leave_control_panel/leave_control_panel.py +++ b/hrms/hr/doctype/leave_control_panel/leave_control_panel.py @@ -3,12 +3,14 @@ import frappe -from frappe import _, msgprint +from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, comma_and, flt +from frappe.utils import cint, flt from erpnext import get_default_company +from hrms.hr.utils import notify_status + class LeaveControlPanel(Document): def validate_fields(self, employees: list): @@ -65,7 +67,7 @@ def create_leave_allocations(self, employees: list) -> dict: allocation.log_error(f"Leave Allocation failed for employee {employee}") failure.append(employee) - self.notify_status("Leave Allocation", failure, success) + notify_status("Leave Allocation", failure, success) return {"failed": failure, "success": success} def create_leave_policy_assignments(self, employees: list) -> dict: @@ -96,7 +98,7 @@ def create_leave_policy_assignments(self, employees: list) -> dict: assignment.log_error(f"Leave Policy Assignment failed for employee {employee}") failure.append(employee) - self.notify_status("Leave Policy Assignment", failure, success) + notify_status("Leave Policy Assignment", failure, success) return {"failed": failure, "success": success} def get_from_to_date(self): @@ -107,44 +109,6 @@ def get_from_to_date(self): else: return self.from_date, self.to_date - def notify_status(self, doctype: str, failure: list, success: list) -> None: - frappe.clear_messages() - - msg = "" - title = "" - if failure: - msg += _("Failed to create/submit {0} for employees:").format(doctype) - msg += " " + comma_and(failure, False) + "
" - msg += ( - _("Check {0} for more details") - .format("{1}") - .format(doctype, _("Error Log")) - ) - - if success: - title = _("Partial Success") - msg += "
" - else: - title = _("Creation Failed") - else: - title = _("Success") - - if success: - msg += _("Successfully created {0} records for:").format(doctype) - msg += " " + comma_and(success, False) - - if failure: - indicator = "orange" if success else "red" - else: - indicator = "green" - - msgprint( - msg, - indicator=indicator, - title=title, - is_minimizable=True, - ) - @frappe.whitelist() def get_employees(self, advanced_filters: list) -> list: from_date, to_date = self.get_from_to_date() diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index ec8a7a787e..e7ac23d239 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -6,6 +6,7 @@ from frappe.model.document import Document from frappe.utils import ( add_days, + comma_and, cstr, flt, format_datetime, @@ -773,3 +774,42 @@ def get_ec_matching_query(bank_account, company, exact_match, from_date=None, to AND mode_of_payment in {mode_of_payments} {filter_by_date} """ + + +def notify_status(doctype: str, failure: list, success: list) -> None: + frappe.clear_messages() + + msg = "" + title = "" + if failure: + msg += _("Failed to create/submit {0} for employees:").format(doctype) + msg += " " + comma_and(failure, False) + "
" + msg += ( + _("Check {0} for more details") + .format("{1}") + .format(doctype, _("Error Log")) + ) + + if success: + title = _("Partial Success") + msg += "
" + else: + title = _("Creation Failed") + else: + title = _("Success") + + if success: + msg += _("Successfully created {0} for employees:").format(doctype) + msg += " " + comma_and(success, False) + + if failure: + indicator = "orange" if success else "red" + else: + indicator = "green" + + frappe.msgprint( + msg, + indicator=indicator, + title=title, + is_minimizable=True, + ) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json index 89ddd4c163..dcf88bd24b 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json @@ -1,6 +1,6 @@ { "actions": [], - "allow_rename": 1, + "allow_copy": 1, "creation": "2024-01-25 12:52:26.250137", "doctype": "DocType", "engine": "InnoDB", @@ -142,10 +142,10 @@ "read_only": 1 } ], - "index_web_pages_for_search": 1, + "hide_toolbar": 1, "issingle": 1, "links": [], - "modified": "2024-01-29 18:47:02.551273", + "modified": "2024-02-01 16:03:03.189628", "modified_by": "Administrator", "module": "Payroll", "name": "Bulk Salary Structure Assignment", @@ -162,6 +162,7 @@ "write": 1 } ], + "read_only": 1, "sort_field": "modified", "sort_order": "DESC", "states": [] diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 33b0b01f97..a0d5c968a1 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -5,6 +5,7 @@ from frappe import _ from frappe.model.document import Document +from hrms.hr.utils import notify_status from hrms.payroll.doctype.salary_structure.salary_structure import ( create_salary_structure_assignment, ) @@ -60,29 +61,31 @@ def bulk_assign_structure(self, employees: list): def _bulk_assign_structure(): success, failure = [], [] count = 0 - for d in employees: - savepoint = "before_allocation_submission" - frappe.db.savepoint(savepoint) - try: - create_salary_structure_assignment( - employee=d["employee"], - salary_structure=self.salary_structure, - company=self.company, - currency=self.currency, - payroll_payable_account=self.payroll_payable_account, - from_date=self.from_date, - base=d["base"], - variable=d["variable"], - income_tax_slab=self.income_tax_slab, - ) - success.append(d["employee"]) - count += 1 - frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) + savepoint = "before_assignments_submission" + frappe.db.savepoint(savepoint) - except Exception: + for d in employees: + assignment = create_salary_structure_assignment( + employee=d["employee"], + salary_structure=self.salary_structure, + company=self.company, + currency=self.currency, + payroll_payable_account=self.payroll_payable_account, + from_date=self.from_date, + base=d["base"], + variable=d["variable"], + income_tax_slab=self.income_tax_slab, + ) + if not assignment: frappe.db.rollback(save_point=savepoint) failure.append(d["employee"]) + continue + + success.append(d["employee"]) + count += 1 + frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) + notify_status("Salary Structure Assignment", failure, success) return {"success": success, "failure": failure} if len(employees) <= 20: diff --git a/hrms/payroll/doctype/salary_structure/salary_structure.py b/hrms/payroll/doctype/salary_structure/salary_structure.py index 8b69d75ec4..118c2d7a88 100644 --- a/hrms/payroll/doctype/salary_structure/salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/salary_structure.py @@ -200,39 +200,44 @@ def create_salary_structure_assignment( variable=None, income_tax_slab=None, ): - if not payroll_payable_account: - payroll_payable_account = frappe.db.get_value( - "Company", company, "default_payroll_payable_account" - ) + try: + assignment = frappe.new_doc("Salary Structure Assignment") + if not payroll_payable_account: - frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults')) - payroll_payable_account_currency = frappe.db.get_value( - "Account", payroll_payable_account, "account_currency" - ) - company_curency = erpnext.get_company_currency(company) - if ( - payroll_payable_account_currency != currency - and payroll_payable_account_currency != company_curency - ): - frappe.throw( - _("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format( - currency, company_curency + payroll_payable_account = frappe.db.get_value( + "Company", company, "default_payroll_payable_account" ) + if not payroll_payable_account: + frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults')) + payroll_payable_account_currency = frappe.db.get_value( + "Account", payroll_payable_account, "account_currency" ) + company_curency = erpnext.get_company_currency(company) + if ( + payroll_payable_account_currency != currency + and payroll_payable_account_currency != company_curency + ): + frappe.throw( + _("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format( + currency, company_curency + ) + ) - assignment = frappe.new_doc("Salary Structure Assignment") - assignment.employee = employee - assignment.salary_structure = salary_structure - assignment.company = company - assignment.currency = currency - assignment.payroll_payable_account = payroll_payable_account - assignment.from_date = from_date - assignment.base = base - assignment.variable = variable - assignment.income_tax_slab = income_tax_slab - assignment.save(ignore_permissions=True) - assignment.submit() - return assignment.name + assignment.employee = employee + assignment.salary_structure = salary_structure + assignment.company = company + assignment.currency = currency + assignment.payroll_payable_account = payroll_payable_account + assignment.from_date = from_date + assignment.base = base + assignment.variable = variable + assignment.income_tax_slab = income_tax_slab + assignment.save(ignore_permissions=True) + assignment.submit() + return assignment.name + + except Exception: + assignment.log_error(f"Salary Structure Assignment failed for employee {employee}") def get_existing_assignments(employees, salary_structure, from_date): From b7c11a13397045dda45d443716cf659caab45dec Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 8 Feb 2024 19:26:54 +0530 Subject: [PATCH 49/79] feat: add number validation for base & variable (cherry picked from commit 4297ea6f2ac0f9f48c3bb26a5c692fec9b4ac157) --- .../bulk_salary_structure_assignment.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 7023ecc08c..16e1a3e415 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -164,6 +164,27 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { cellHeight: 35, noDataMessage: __("No Data"), disableReorderColumn: true, + getEditor: function (colIndex, rowIndex, value, parent, column) { + if (!["base", "variable"].includes(column.name)) return; + const $input = document.createElement("input"); + $input.className = "dt-input h-100"; + $input.type = "number"; + $input.min = 0; + parent.appendChild($input); + + return { + initValue(value) { + $input.focus(); + $input.value = value; + }, + setValue(value) { + $input.value = value; + }, + getValue() { + return $input.value; + }, + }; + }, }; frm.employees_datatable = new frappe.DataTable( frm.employee_wrapper.get(0), From 64ddfa27d7d2e2de195a40453e567828b59fd8c8 Mon Sep 17 00:00:00 2001 From: krantheman Date: Fri, 9 Feb 2024 19:03:44 +0530 Subject: [PATCH 50/79] feat: add Bulk Salary Structure Assignment to Salary Payout workspace (cherry picked from commit ebdec20a5d17950019a1b58f7f0037f20570b8dd) --- .../workspace/salary_payout/salary_payout.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/hrms/payroll/workspace/salary_payout/salary_payout.json b/hrms/payroll/workspace/salary_payout/salary_payout.json index c301e98baf..9854c5672b 100644 --- a/hrms/payroll/workspace/salary_payout/salary_payout.json +++ b/hrms/payroll/workspace/salary_payout/salary_payout.json @@ -314,7 +314,8 @@ "hidden": 0, "is_query_report": 0, "label": "Payroll", - "link_count": 3, + "link_count": 4, + "link_type": "DocType", "onboard": 0, "type": "Card Break" }, @@ -328,6 +329,16 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Bulk Salary Structure Assignment", + "link_count": 0, + "link_to": "Bulk Salary Structure Assignment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -349,7 +360,7 @@ "type": "Link" } ], - "modified": "2023-06-27 13:09:28.191836", + "modified": "2024-02-09 19:02:28.208503", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Payout", From cd4c32aeae6b4835f3c8d7445a74288e459e5d5a Mon Sep 17 00:00:00 2001 From: krantheman Date: Fri, 9 Feb 2024 19:09:41 +0530 Subject: [PATCH 51/79] fix: user perms (cherry picked from commit e9c791b39a91918f8404978182307e2a0d9dcd1e) --- .../bulk_salary_structure_assignment.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json index dcf88bd24b..1d2c7b813f 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.json @@ -145,7 +145,7 @@ "hide_toolbar": 1, "issingle": 1, "links": [], - "modified": "2024-02-01 16:03:03.189628", + "modified": "2024-02-09 19:08:44.834075", "modified_by": "Administrator", "module": "Payroll", "name": "Bulk Salary Structure Assignment", @@ -153,11 +153,10 @@ "permissions": [ { "create": 1, - "delete": 1, "email": 1, "print": 1, "read": 1, - "role": "System Manager", + "role": "HR User", "share": 1, "write": 1 } From 6a2b2228d2f2626aab423a27186755005d544a81 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 21 Feb 2024 13:46:55 +0530 Subject: [PATCH 52/79] feat: add update Base/Variable for multiple rows (cherry picked from commit 5ebdf1f9d14c52fc3e3c649054d51e9cc2026c09) --- .../bulk_salary_structure_assignment.js | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 16e1a3e415..790228923c 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -11,6 +11,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.trigger("set_primary_action"); await frm.trigger("set_payroll_payable_account"); frm.trigger("get_employees"); + frm.trigger("render_update_button"); }, from_date(frm) { @@ -96,6 +97,45 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, + render_update_button(frm) { + ["Base", "Variable"].forEach((d) => + frm.add_custom_button( + __(d), + function () { + const dialog = new frappe.ui.Dialog({ + title: __("Set {0} for Selected Employees", [__(d)]), + fields: [ + { + label: __(d), + fieldname: d, + fieldtype: "Currency", + }, + ], + primary_action_label: __("Update"), + primary_action(values) { + const col_idx = frm.employees_datatable.datamanager.columns.find( + (col) => col.content === d + ).colIndex; + const checked_rows = + frm.employees_datatable.rowmanager.getCheckedRows(); + checked_rows.forEach((row_idx) => { + frm.employees_datatable.cellmanager.updateCell( + col_idx, + row_idx, + values[d], + true + ); + }); + dialog.hide(); + }, + }); + dialog.show(); + }, + __("Update") + ) + ); + }, + setup_filter_group(frm) { const filter_wrapper = frm.fields_dict.filter_list.$wrapper; filter_wrapper.empty(); @@ -135,12 +175,12 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.set_df_property("advanced_filters_section", "collapsible", 0); frm.employees = r.message.map((d) => ({ base: 0, variable: 0, ...d })); - frm.events.render_employees_table(frm); + frm.events.render_employees_datatable(frm); }); }, - render_employees_table(frm) { - const columns = frm.events.columns_for_employees_table(); + render_employees_datatable(frm) { + const columns = frm.events.columns_for_employees_datatable(); if (frm.employees_datatable) { frm.employees_datatable.rowmanager.checkMap = []; @@ -164,7 +204,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { cellHeight: 35, noDataMessage: __("No Data"), disableReorderColumn: true, - getEditor: function (colIndex, rowIndex, value, parent, column) { + getEditor(colIndex, rowIndex, value, parent, column) { if (!["base", "variable"].includes(column.name)) return; const $input = document.createElement("input"); $input.className = "dt-input h-100"; @@ -192,7 +232,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, - columns_for_employees_table() { + columns_for_employees_datatable() { return [ { name: "employee", @@ -226,12 +266,11 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, assign_structure(frm) { - const check_map = frm.employees_datatable.rowmanager.checkMap; + const checked_rows = frm.employees_datatable.rowmanager.getCheckedRows(); const selected_employees = []; - check_map.forEach((is_checked, idx) => { - if (is_checked) - selected_employees.push(frm.employees_datatable.datamanager.data[idx]); - }); + checked_rows.forEach((idx) => + selected_employees.push(frm.employees_datatable.datamanager.data[idx]) + ); frm .call({ method: "bulk_assign_structure", From b2eae787a66d168a2e6759cc5640a38c71b587f2 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 21 Feb 2024 16:23:14 +0530 Subject: [PATCH 53/79] feat: load default base pay based on employee grade (cherry picked from commit a4f930a56ac2229f2b5f34337b40b2b96a3fde70) --- .../bulk_salary_structure_assignment.js | 2 +- .../bulk_salary_structure_assignment.py | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 790228923c..d1abdeb55b 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -174,7 +174,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.set_df_property("quick_filters_section", "collapsible", 0); frm.set_df_property("advanced_filters_section", "collapsible", 0); - frm.employees = r.message.map((d) => ({ base: 0, variable: 0, ...d })); + frm.employees = r.message; frm.events.render_employees_datatable(frm); }); }, diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index a0d5c968a1..6a8105ee9a 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -1,6 +1,8 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from pypika import functions as fn + import frappe from frappe import _ from frappe.model.document import Document @@ -38,11 +40,21 @@ def get_employees(self, advanced_filters: list) -> list: + quick_filters + advanced_filters ) - return frappe.get_list( - "Employee", - filters=filters, - fields=["employee", "employee_name"], + + Employee = frappe.qb.DocType("Employee") + Grade = frappe.qb.DocType("Employee Grade") + query = ( + frappe.qb.get_query(Employee, filters=filters) + .left_join(Grade) + .on(Employee.grade == Grade.name) + .select( + Employee.employee, + Employee.employee_name, + fn.Coalesce(Grade.default_base_pay, 0).as_("base"), + fn.Coalesce(0).as_("variable"), + ) ) + return query.run(as_dict=True) def validate_fields(self, employees: list): for d in ["salary_structure", "from_date", "company"]: @@ -55,7 +67,7 @@ def validate_fields(self, employees: list): ) @frappe.whitelist() - def bulk_assign_structure(self, employees: list): + def bulk_assign_structure(self, employees: list) -> dict: self.validate_fields(employees) def _bulk_assign_structure(): From c1ce694b0ea3e04cda0b4bfd394db0dc82c2461f Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 21 Feb 2024 17:09:06 +0530 Subject: [PATCH 54/79] fix: use updated values while assigning structure (cherry picked from commit 6e3b5e28554d6f5a8c7d9b12339111841b5b7379) --- .../bulk_salary_structure_assignment.js | 21 ++++++++++++------- .../bulk_salary_structure_assignment.py | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index d1abdeb55b..fb6d1deb78 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -221,7 +221,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { $input.value = value; }, getValue() { - return $input.value; + return Number($input.value); }, }; }, @@ -266,17 +266,24 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, assign_structure(frm) { - const checked_rows = frm.employees_datatable.rowmanager.getCheckedRows(); - const selected_employees = []; - checked_rows.forEach((idx) => - selected_employees.push(frm.employees_datatable.datamanager.data[idx]) - ); + const rows = frm.employees_datatable.getRows(); + const checked_rows_indexes = + frm.employees_datatable.rowmanager.getCheckedRows(); + const checked_rows_content = []; + checked_rows_indexes.forEach((idx) => { + const row_content = {}; + rows[idx].forEach((cell) => { + if (["employee", "base", "variable"].includes(cell.column.name)) + row_content[cell.column.name] = cell.content; + }); + checked_rows_content.push(row_content); + }); frm .call({ method: "bulk_assign_structure", doc: frm.doc, args: { - employees: selected_employees, + employees: checked_rows_content, }, freeze: true, freeze_message: __("Assigning Salary Structure"), diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 6a8105ee9a..a1d6ea93d2 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -44,12 +44,12 @@ def get_employees(self, advanced_filters: list) -> list: Employee = frappe.qb.DocType("Employee") Grade = frappe.qb.DocType("Employee Grade") query = ( - frappe.qb.get_query(Employee, filters=filters) + frappe.qb.get_query( + Employee, fields=[Employee.employee, Employee.employee_name], filters=filters + ) .left_join(Grade) .on(Employee.grade == Grade.name) .select( - Employee.employee, - Employee.employee_name, fn.Coalesce(Grade.default_base_pay, 0).as_("base"), fn.Coalesce(0).as_("variable"), ) From 3db6d8ec87fb1aa216b450f6d0f262d1d5ce16ec Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 21 Feb 2024 17:13:10 +0530 Subject: [PATCH 55/79] feat: add Employee Grade column (cherry picked from commit 8dd864c6a11fce58b4e8653e2ee76e73a4c5311c) --- .../bulk_salary_structure_assignment.js | 7 +++++++ .../bulk_salary_structure_assignment.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index fb6d1deb78..ed81761137 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -248,6 +248,13 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { editable: false, focusable: false, }, + { + name: "grade", + id: "grade", + content: __("Grade"), + editable: false, + focusable: false, + }, { name: "base", id: "base", diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index a1d6ea93d2..a993c4b77c 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -45,7 +45,7 @@ def get_employees(self, advanced_filters: list) -> list: Grade = frappe.qb.DocType("Employee Grade") query = ( frappe.qb.get_query( - Employee, fields=[Employee.employee, Employee.employee_name], filters=filters + Employee, fields=[Employee.employee, Employee.employee_name, Employee.grade], filters=filters ) .left_join(Grade) .on(Employee.grade == Grade.name) From f6233b628f6d4872e2c70ff415c4fae4bf25ef28 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 21 Feb 2024 17:33:21 +0530 Subject: [PATCH 56/79] feat: validate selection of employees while setting Base/Variable (cherry picked from commit 472df9c2e1a99503ca1006165b44c95a3ab4c4cb) --- .../bulk_salary_structure_assignment.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index ed81761137..a9195d7126 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -102,6 +102,17 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.add_custom_button( __(d), function () { + const checked_rows = + frm.employees_datatable.rowmanager.getCheckedRows(); + if (!checked_rows.length) + frappe.throw({ + title: __("No Employees Selected"), + message: __( + "Please select the employees for whom you want to set the {0}.", + [d] + ), + }); + const dialog = new frappe.ui.Dialog({ title: __("Set {0} for Selected Employees", [__(d)]), fields: [ @@ -116,8 +127,6 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { const col_idx = frm.employees_datatable.datamanager.columns.find( (col) => col.content === d ).colIndex; - const checked_rows = - frm.employees_datatable.rowmanager.getCheckedRows(); checked_rows.forEach((row_idx) => { frm.employees_datatable.cellmanager.updateCell( col_idx, From ff83ba755369704b4cacc6f679ec75c9685034ed Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 13:00:38 +0530 Subject: [PATCH 57/79] refactor: render update button only on row selection (cherry picked from commit 4a234da73556396c804511775c9a4677bb04afb2) --- .../bulk_salary_structure_assignment.js | 35 +++++++++++-------- .../bulk_salary_structure_assignment.py | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index a9195d7126..f9a27b2c02 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -11,7 +11,6 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.trigger("set_primary_action"); await frm.trigger("set_payroll_payable_account"); frm.trigger("get_employees"); - frm.trigger("render_update_button"); }, from_date(frm) { @@ -102,17 +101,6 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { frm.add_custom_button( __(d), function () { - const checked_rows = - frm.employees_datatable.rowmanager.getCheckedRows(); - if (!checked_rows.length) - frappe.throw({ - title: __("No Employees Selected"), - message: __( - "Please select the employees for whom you want to set the {0}.", - [d] - ), - }); - const dialog = new frappe.ui.Dialog({ title: __("Set {0} for Selected Employees", [__(d)]), fields: [ @@ -127,7 +115,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { const col_idx = frm.employees_datatable.datamanager.columns.find( (col) => col.content === d ).colIndex; - checked_rows.forEach((row_idx) => { + frm.checked_rows.forEach((row_idx) => { frm.employees_datatable.cellmanager.updateCell( col_idx, row_idx, @@ -143,6 +131,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { __("Update") ) ); + frm.update_button_rendered = true; }, setup_filter_group(frm) { @@ -189,7 +178,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, render_employees_datatable(frm) { - const columns = frm.events.columns_for_employees_datatable(); + const columns = frm.events.employees_datatable_columns(); if (frm.employees_datatable) { frm.employees_datatable.rowmanager.checkMap = []; @@ -234,6 +223,11 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, }; }, + events: { + onCheckRow() { + frm.trigger("handle_row_check"); + }, + }, }; frm.employees_datatable = new frappe.DataTable( frm.employee_wrapper.get(0), @@ -241,7 +235,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, - columns_for_employees_datatable() { + employees_datatable_columns() { return [ { name: "employee", @@ -281,6 +275,17 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { })); }, + handle_row_check(frm) { + frm.checked_rows = frm.employees_datatable.rowmanager.getCheckedRows(); + if (!frm.checked_rows.length && frm.update_button_rendered) { + ["Base", "Variable"].forEach((d) => + frm.remove_custom_button(__(d), __("Update")) + ); + frm.update_button_rendered = false; + } else if (frm.checked_rows.length && !frm.update_button_rendered) + frm.trigger("render_update_button"); + }, + assign_structure(frm) { const rows = frm.employees_datatable.getRows(); const checked_rows_indexes = diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index a993c4b77c..edfe093ed0 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -62,7 +62,7 @@ def validate_fields(self, employees: list): frappe.throw(_("{0} is required").format(self.meta.get_label(d)), title=_("Missing Field")) if not employees: frappe.throw( - _("Please select the employees to whom the salary structure should be assigned"), + _("Please select the employees to whom the Salary Structure should be assigned."), title=_("No Employees Selected"), ) From d82dd0016f8e0686dc0e0c057661eee4ac8e29d6 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 13:29:36 +0530 Subject: [PATCH 58/79] chore: improve readability (cherry picked from commit 9bd05c395808fe7cbfc34ad61a901d498fd0e22f) --- .../bulk_salary_structure_assignment.js | 94 ++++++++++--------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index f9a27b2c02..847bea4281 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -3,7 +3,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { setup(frm) { - frm.trigger("set_query"); + frm.trigger("set_queries"); frm.trigger("setup_filter_group"); }, @@ -49,7 +49,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }); }, - set_query(frm) { + set_queries(frm) { frm.set_query("salary_structure", function () { return { filters: { @@ -96,44 +96,6 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, - render_update_button(frm) { - ["Base", "Variable"].forEach((d) => - frm.add_custom_button( - __(d), - function () { - const dialog = new frappe.ui.Dialog({ - title: __("Set {0} for Selected Employees", [__(d)]), - fields: [ - { - label: __(d), - fieldname: d, - fieldtype: "Currency", - }, - ], - primary_action_label: __("Update"), - primary_action(values) { - const col_idx = frm.employees_datatable.datamanager.columns.find( - (col) => col.content === d - ).colIndex; - frm.checked_rows.forEach((row_idx) => { - frm.employees_datatable.cellmanager.updateCell( - col_idx, - row_idx, - values[d], - true - ); - }); - dialog.hide(); - }, - }); - dialog.show(); - }, - __("Update") - ) - ); - frm.update_button_rendered = true; - }, - setup_filter_group(frm) { const filter_wrapper = frm.fields_dict.filter_list.$wrapper; filter_wrapper.empty(); @@ -204,6 +166,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { disableReorderColumn: true, getEditor(colIndex, rowIndex, value, parent, column) { if (!["base", "variable"].includes(column.name)) return; + const $input = document.createElement("input"); $input.className = "dt-input h-100"; $input.type = "number"; @@ -275,23 +238,61 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { })); }, + render_update_button(frm) { + ["Base", "Variable"].forEach((d) => + frm.add_custom_button( + __(d), + function () { + const dialog = new frappe.ui.Dialog({ + title: __("Set {0} for Selected Employees", [__(d)]), + fields: [ + { + label: __(d), + fieldname: d, + fieldtype: "Currency", + }, + ], + primary_action_label: __("Update"), + primary_action(values) { + const col_idx = frm.employees_datatable.datamanager.columns.find( + (col) => col.content === d + ).colIndex; + frm.checked_rows_indexes.forEach((row_idx) => { + frm.employees_datatable.cellmanager.updateCell( + col_idx, + row_idx, + values[d], + true + ); + }); + dialog.hide(); + }, + }); + dialog.show(); + }, + __("Update") + ) + ); + frm.update_button_rendered = true; + }, + handle_row_check(frm) { - frm.checked_rows = frm.employees_datatable.rowmanager.getCheckedRows(); - if (!frm.checked_rows.length && frm.update_button_rendered) { + frm.checked_rows_indexes = + frm.employees_datatable.rowmanager.getCheckedRows(); + if (!frm.checked_rows_indexes.length && frm.update_button_rendered) { ["Base", "Variable"].forEach((d) => frm.remove_custom_button(__(d), __("Update")) ); frm.update_button_rendered = false; - } else if (frm.checked_rows.length && !frm.update_button_rendered) + } else if (frm.checked_rows_indexes.length && !frm.update_button_rendered) frm.trigger("render_update_button"); }, assign_structure(frm) { const rows = frm.employees_datatable.getRows(); - const checked_rows_indexes = - frm.employees_datatable.rowmanager.getCheckedRows(); const checked_rows_content = []; - checked_rows_indexes.forEach((idx) => { + + frm.checked_rows_indexes.forEach((idx) => { const row_content = {}; rows[idx].forEach((cell) => { if (["employee", "base", "variable"].includes(cell.column.name)) @@ -299,6 +300,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }); checked_rows_content.push(row_content); }); + frm .call({ method: "bulk_assign_structure", From af2d0eccf99d05f550227c20a6d8253a74770a73 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 15:15:36 +0530 Subject: [PATCH 59/79] feat: prevent loading of employees without From Date (cherry picked from commit a58d00d0e3c7332de8cf0d2e73fb14aae46f7e8f) --- .../bulk_salary_structure_assignment.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 847bea4281..7e6b44529a 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -121,6 +121,9 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, get_employees(frm) { + if (!frm.doc.from_date) + return frm.events.render_employees_datatable(frm, []); + frm .call({ method: "get_employees", @@ -129,22 +132,25 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, doc: frm.doc, }) - .then((r) => { - // section automatically collapses on applying a single filter - frm.set_df_property("quick_filters_section", "collapsible", 0); - frm.set_df_property("advanced_filters_section", "collapsible", 0); - - frm.employees = r.message; - frm.events.render_employees_datatable(frm); - }); + .then((r) => frm.events.render_employees_datatable(frm, r.message)); }, - render_employees_datatable(frm) { + render_employees_datatable(frm, employees) { + // section automatically collapses on applying a single filter + frm.set_df_property("quick_filters_section", "collapsible", 0); + frm.set_df_property("advanced_filters_section", "collapsible", 0); + const columns = frm.events.employees_datatable_columns(); + const no_data_message = __( + frm.doc.from_date + ? "There are no Employees without a Salary Structure Assignment on this date based on the given filters." + : "Please select From Date." + ); if (frm.employees_datatable) { frm.employees_datatable.rowmanager.checkMap = []; - frm.employees_datatable.refresh(frm.employees, columns); + frm.employees_datatable.options.noDataMessage = no_data_message; + frm.employees_datatable.refresh(employees, columns); return; } @@ -154,7 +160,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); const datatable_options = { columns: columns, - data: frm.employees, + data: employees, checkboxColumn: true, checkedRowStatus: false, serialNoColumn: false, @@ -162,7 +168,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { inlineFilters: true, layout: "fluid", cellHeight: 35, - noDataMessage: __("No Data"), + noDataMessage: no_data_message, disableReorderColumn: true, getEditor(colIndex, rowIndex, value, parent, column) { if (!["base", "variable"].includes(column.name)) return; From 2dd3de718e654011cacad8da7037bebd853e2ff6 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 16:52:59 +0530 Subject: [PATCH 60/79] test: add test_get_employees (cherry picked from commit 366c9f8502ad866d57899cad1d302a77a01e6cb3) --- .../test_bulk_salary_structure_assignment.py | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py index 679674ff5e..59ece52858 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py @@ -1,9 +1,67 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe from frappe.tests.utils import FrappeTestCase +from frappe.utils import getdate + +from erpnext.setup.doctype.employee.test_employee import make_employee + +from hrms.payroll.doctype.bulk_salary_structure_assignment.bulk_salary_structure_assignment import ( + BulkSalaryStructureAssignment, +) +from hrms.payroll.doctype.salary_structure.test_salary_structure import ( + create_employee_grade, + make_salary_structure, +) +from hrms.tests.test_utils import create_company, create_department class TestBulkSalaryStructureAssignment(FrappeTestCase): - pass + def setUp(self): + create_company() + create_department("Accounts") + create_employee_grade("Test Grade") + + # employee grade with default base pay 50000 + self.emp1 = make_employee( + "employee1@bssa.com", company="_Test Company", department="Accounts", grade="Test Grade" + ) + self.emp2 = make_employee("employee2@bssa.com", company="_Test Company", department="Accounts") + self.emp3 = make_employee("employee3@bssa.com", company="_Test Company", department="Accounts") + # no department + self.emp4 = make_employee("employee4@bssa.com", company="_Test Company") + # different domain in employee_name + self.emp5 = make_employee("employee5@test.com", company="_Test Company", department="Accounts") + + def tearDown(self): + frappe.db.rollback() + + def test_get_employees(self): + today = getdate() + + # create structure and assign to emp2 + make_salary_structure("Salary Structure 1", "Monthly", self.emp2, today, company="_Test Company") + + args = { + "doctype": "Bulk Salary Structure Assignment", + "from_date": today, + "department": "Accounts", + } + bulk_assignment = BulkSalaryStructureAssignment(args) + + advanced_filters = [["Employee", "employee_name", "like", "%bssa%"]] + employees = bulk_assignment.get_employees(advanced_filters) + employee_names = [d.name for d in employees] + + # employee already having an assignment + self.assertNotIn(self.emp2, employee_names) + # department quick filter applied + self.assertNotIn(self.emp4, employee_names) + # employee_name advanced filter applied + self.assertNotIn(self.emp5, employee_names) + # employee grade default base pay fetched + self.assertEqual(employees[0].base, 50000) + # no employee grade + self.assertFalse(employees[1].base) + self.assertEqual(len(employees), 2) From 3626c7aa2a083eef34f51e416a88917a662856e9 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 17:02:51 +0530 Subject: [PATCH 61/79] chore: move create_employee_grade to test_utils (cherry picked from commit 2063c1e22490f74302ed67796d37a4b20e334e2b) --- .../test_bulk_salary_structure_assignment.py | 11 ++++------- .../salary_structure/test_salary_structure.py | 15 ++------------- hrms/tests/test_utils.py | 13 +++++++++++++ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py index 59ece52858..9f4cc5f281 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py @@ -10,18 +10,15 @@ from hrms.payroll.doctype.bulk_salary_structure_assignment.bulk_salary_structure_assignment import ( BulkSalaryStructureAssignment, ) -from hrms.payroll.doctype.salary_structure.test_salary_structure import ( - create_employee_grade, - make_salary_structure, -) -from hrms.tests.test_utils import create_company, create_department +from hrms.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure +from hrms.tests.test_utils import create_company, create_department, create_employee_grade class TestBulkSalaryStructureAssignment(FrappeTestCase): def setUp(self): create_company() create_department("Accounts") - create_employee_grade("Test Grade") + self.grade = create_employee_grade("Test Grade") # employee grade with default base pay 50000 self.emp1 = make_employee( @@ -61,7 +58,7 @@ def test_get_employees(self): # employee_name advanced filter applied self.assertNotIn(self.emp5, employee_names) # employee grade default base pay fetched - self.assertEqual(employees[0].base, 50000) + self.assertEqual(employees[0].base, self.grade.default_base_pay) # no employee grade self.assertFalse(employees[1].base) self.assertEqual(len(employees), 2) diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py index b325237c73..68e76ebefd 100644 --- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py @@ -22,6 +22,7 @@ create_salary_structure_assignment as create_assignment, ) from hrms.payroll.doctype.salary_structure.salary_structure import make_salary_slip +from hrms.tests.test_utils import create_employee_grade test_dependencies = ["Fiscal Year"] @@ -284,16 +285,4 @@ def create_salary_structure_assignment( def get_payable_account(company=None): if not company: company = erpnext.get_default_company() - return frappe.db.get_value("Company", company, "default_payroll_payable_account") - - -def create_employee_grade(grade, default_structure=None): - if not frappe.db.exists("Employee Grade", grade): - frappe.get_doc( - { - "doctype": "Employee Grade", - "__newname": grade, - "default_salary_structure": default_structure, - "default_base_pay": 50000, - } - ).insert() + return frappe.db.get_value("Company", company, "default_payroll_payable_account") \ No newline at end of file diff --git a/hrms/tests/test_utils.py b/hrms/tests/test_utils.py index 33ab8615ea..7ca2f7adfa 100644 --- a/hrms/tests/test_utils.py +++ b/hrms/tests/test_utils.py @@ -118,6 +118,19 @@ def create_department(name: str, company: str = "_Test Company") -> str: return department.name +def create_employee_grade(grade: str, default_structure: str = None, default_base: float = 50000): + if frappe.db.exists("Employee Grade", grade): + return frappe.get_doc("Employee Grade", grade) + return frappe.get_doc( + { + "doctype": "Employee Grade", + "__newname": grade, + "default_salary_structure": default_structure, + "default_base_pay": default_base, + } + ).insert() + + def create_job_applicant(**args): args = frappe._dict(args) filters = { From c64a6853cd09ee8262301a471ab86d0c4c9f604b Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 17:44:18 +0530 Subject: [PATCH 62/79] test: add test_bulk_assign_structure (cherry picked from commit 50ba3f0bbb81952cd0f0232eaad21a9d62e10fd6) --- .../test_bulk_salary_structure_assignment.py | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py index 9f4cc5f281..fc1aaaad63 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/test_bulk_salary_structure_assignment.py @@ -60,5 +60,46 @@ def test_get_employees(self): # employee grade default base pay fetched self.assertEqual(employees[0].base, self.grade.default_base_pay) # no employee grade - self.assertFalse(employees[1].base) + self.assertEqual(employees[1].base, 0) self.assertEqual(len(employees), 2) + + def test_bulk_assign_structure(self): + today = getdate() + salary_structure = make_salary_structure( + "Salary Structure 1", "Monthly", company="_Test Company" + ) + + args = { + "doctype": "Bulk Salary Structure Assignment", + "salary_structure": salary_structure, + "from_date": today, + "company": "_Test Company", + } + bulk_assignment = BulkSalaryStructureAssignment(args) + + employees = [ + {"employee": self.emp1, "base": 50000, "variable": 2000}, + {"employee": self.emp2, "base": 40000, "variable": 0}, + ] + bulk_assignment.bulk_assign_structure(employees) + + ssa1 = frappe.get_value( + "Salary Structure Assignment", + {"employee": self.emp1}, + ["salary_structure", "from_date", "company", "base", "variable"], + as_dict=1, + ) + self.assertEqual(ssa1.salary_structure, salary_structure.name) + self.assertEqual(ssa1.from_date, today) + self.assertEqual(ssa1.company, "_Test Company") + self.assertEqual(ssa1.base, 50000) + self.assertEqual(ssa1.variable, 2000) + + ssa2 = frappe.get_value( + "Salary Structure Assignment", + {"employee": self.emp2}, + ["base", "variable"], + as_dict=1, + ) + self.assertEqual(ssa2.base, 40000) + self.assertEqual(ssa2.variable, 0) From b67a13d5753b1ee74d3654e382c1fcfe213f8a32 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 22 Feb 2024 17:46:02 +0530 Subject: [PATCH 63/79] chore: fix formatting (cherry picked from commit dc39abf522134f10d61fccc8187629018d9f4c13) --- hrms/payroll/doctype/salary_structure/test_salary_structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py index 68e76ebefd..344005ba15 100644 --- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py @@ -285,4 +285,4 @@ def create_salary_structure_assignment( def get_payable_account(company=None): if not company: company = erpnext.get_default_company() - return frappe.db.get_value("Company", company, "default_payroll_payable_account") \ No newline at end of file + return frappe.db.get_value("Company", company, "default_payroll_payable_account") From 55f251ac01ff3d887eb0381384547dedc218a7f5 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 29 Feb 2024 16:24:13 +0530 Subject: [PATCH 64/79] chore: rename notify_status function (cherry picked from commit 3a9e51ac1636f71a2add4c842fa408e3ed7e2e54) --- hrms/hr/doctype/leave_control_panel/leave_control_panel.py | 6 +++--- hrms/hr/utils.py | 2 +- .../bulk_salary_structure_assignment.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hrms/hr/doctype/leave_control_panel/leave_control_panel.py b/hrms/hr/doctype/leave_control_panel/leave_control_panel.py index d2e66a1b87..3e5640bbda 100644 --- a/hrms/hr/doctype/leave_control_panel/leave_control_panel.py +++ b/hrms/hr/doctype/leave_control_panel/leave_control_panel.py @@ -9,7 +9,7 @@ from erpnext import get_default_company -from hrms.hr.utils import notify_status +from hrms.hr.utils import notify_bulk_action_status class LeaveControlPanel(Document): @@ -67,7 +67,7 @@ def create_leave_allocations(self, employees: list) -> dict: allocation.log_error(f"Leave Allocation failed for employee {employee}") failure.append(employee) - notify_status("Leave Allocation", failure, success) + notify_bulk_action_status("Leave Allocation", failure, success) return {"failed": failure, "success": success} def create_leave_policy_assignments(self, employees: list) -> dict: @@ -98,7 +98,7 @@ def create_leave_policy_assignments(self, employees: list) -> dict: assignment.log_error(f"Leave Policy Assignment failed for employee {employee}") failure.append(employee) - notify_status("Leave Policy Assignment", failure, success) + notify_bulk_action_status("Leave Policy Assignment", failure, success) return {"failed": failure, "success": success} def get_from_to_date(self): diff --git a/hrms/hr/utils.py b/hrms/hr/utils.py index e7ac23d239..54299361d5 100644 --- a/hrms/hr/utils.py +++ b/hrms/hr/utils.py @@ -776,7 +776,7 @@ def get_ec_matching_query(bank_account, company, exact_match, from_date=None, to """ -def notify_status(doctype: str, failure: list, success: list) -> None: +def notify_bulk_action_status(doctype: str, failure: list, success: list) -> None: frappe.clear_messages() msg = "" diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index edfe093ed0..99c322052d 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -7,7 +7,7 @@ from frappe import _ from frappe.model.document import Document -from hrms.hr.utils import notify_status +from hrms.hr.utils import notify_bulk_action_status from hrms.payroll.doctype.salary_structure.salary_structure import ( create_salary_structure_assignment, ) @@ -97,7 +97,7 @@ def _bulk_assign_structure(): count += 1 frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) - notify_status("Salary Structure Assignment", failure, success) + notify_bulk_action_status("Salary Structure Assignment", failure, success) return {"success": success, "failure": failure} if len(employees) <= 20: From e2d118870113a3c2e9c4e0b30a1468b4765671d2 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 29 Feb 2024 16:34:05 +0530 Subject: [PATCH 65/79] feat: add Bulk Salary Structure Assignment button to Salary Structure List View (cherry picked from commit da12fbf534f742c4126f39ab0a890e326088167b) --- .../doctype/salary_structure/salary_structure_list.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 hrms/payroll/doctype/salary_structure/salary_structure_list.js diff --git a/hrms/payroll/doctype/salary_structure/salary_structure_list.js b/hrms/payroll/doctype/salary_structure/salary_structure_list.js new file mode 100644 index 0000000000..5e94537ad3 --- /dev/null +++ b/hrms/payroll/doctype/salary_structure/salary_structure_list.js @@ -0,0 +1,10 @@ +frappe.listview_settings["Salary Structure"] = { + onload: function (list_view) { + list_view.page.add_inner_button( + __("Bulk Salary Structure Assignment"), + function () { + frappe.set_route("Form", "Bulk Salary Structure Assignment"); + } + ); + }, +}; From 6ca9ba429616d8d69fd8e2445829caf66be382f3 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 29 Feb 2024 16:39:16 +0530 Subject: [PATCH 66/79] chore: use ConstantColumn instead of Coalesce (cherry picked from commit af5702a5aa363d74debf2e8adf692cd645f9495b) --- .../bulk_salary_structure_assignment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 99c322052d..853d1c34d0 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -1,11 +1,11 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from pypika import functions as fn - import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder.custom import ConstantColumn +from frappe.query_builder.functions import Coalesce from hrms.hr.utils import notify_bulk_action_status from hrms.payroll.doctype.salary_structure.salary_structure import ( @@ -50,8 +50,8 @@ def get_employees(self, advanced_filters: list) -> list: .left_join(Grade) .on(Employee.grade == Grade.name) .select( - fn.Coalesce(Grade.default_base_pay, 0).as_("base"), - fn.Coalesce(0).as_("variable"), + Coalesce(Grade.default_base_pay, 0).as_("base"), + ConstantColumn(0).as_("variable"), ) ) return query.run(as_dict=True) From e496a9567097d515c954ebf3d6e01165d2fad0e4 Mon Sep 17 00:00:00 2001 From: Akash Tom <61287991+krantheman@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:43:36 +0530 Subject: [PATCH 67/79] fix: queue timeout Co-authored-by: Rucha Mahabal (cherry picked from commit ce3b85c99fa076c4c384d46388069ac7ad272e6b) --- .../bulk_salary_structure_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 853d1c34d0..b772390d84 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -103,4 +103,4 @@ def _bulk_assign_structure(): if len(employees) <= 20: return _bulk_assign_structure() - return frappe.enqueue(_bulk_assign_structure, timeout=600) + return frappe.enqueue(_bulk_assign_structure, timeout=3000) From 5d7957aed72828454128d365cb851385c91defdb Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 29 Feb 2024 17:47:52 +0530 Subject: [PATCH 68/79] feat: exclude unjoined and relieved employees while fetching employees (cherry picked from commit 82ad53f09f7705c847b20066c0fd4b526ea8338c) --- .../bulk_salary_structure_assignment.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index b772390d84..0e8dba81f1 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -6,6 +6,7 @@ from frappe.model.document import Document from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Coalesce +from frappe.query_builder.terms import SubQuery from hrms.hr.utils import notify_bulk_action_status from hrms.payroll.doctype.salary_structure.salary_structure import ( @@ -16,15 +17,6 @@ class BulkSalaryStructureAssignment(Document): @frappe.whitelist() def get_employees(self, advanced_filters: list) -> list: - Assignment = frappe.qb.DocType("Salary Structure Assignment") - employees_with_assignments = ( - frappe.qb.from_(Assignment) - .select(Assignment.employee) - .distinct() - .where((Assignment.from_date == self.from_date) & (Assignment.docstatus == 1)) - .run(pluck=True) - ) - quick_filter_fields = [ "company", "employment_type", @@ -33,19 +25,30 @@ def get_employees(self, advanced_filters: list) -> list: "designation", "grade", ] - quick_filters = [[d, "=", self.get(d)] for d in quick_filter_fields if self.get(d)] + filters = [[d, "=", self.get(d)] for d in quick_filter_fields if self.get(d)] + filters += advanced_filters - filters = ( - [["status", "=", "Active"], ["employee", "not in", employees_with_assignments]] - + quick_filters - + advanced_filters + Assignment = frappe.qb.DocType("Salary Structure Assignment") + employees_with_assignments = SubQuery( + frappe.qb.from_(Assignment) + .select(Assignment.employee) + .distinct() + .where((Assignment.from_date == self.from_date) & (Assignment.docstatus == 1)) ) Employee = frappe.qb.DocType("Employee") Grade = frappe.qb.DocType("Employee Grade") query = ( frappe.qb.get_query( - Employee, fields=[Employee.employee, Employee.employee_name, Employee.grade], filters=filters + Employee, + fields=[Employee.employee, Employee.employee_name, Employee.grade], + filters=filters, + ) + .where( + (Employee.status == "Active") + & (Employee.date_of_joining <= self.from_date) + & ((Employee.relieving_date > self.from_date) | (Employee.relieving_date.isnull())) + & (Employee.employee.notin(employees_with_assignments)) ) .left_join(Grade) .on(Employee.grade == Grade.name) From 1fc42301dd5dc1dccc0679918eb315966e434033 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 29 Feb 2024 18:11:33 +0530 Subject: [PATCH 69/79] chore: better error msg (cherry picked from commit 04e6b61aacc2e43ad0115a3b06f0f16654a81227) --- .../bulk_salary_structure_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 0e8dba81f1..e57f87d7ef 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -65,7 +65,7 @@ def validate_fields(self, employees: list): frappe.throw(_("{0} is required").format(self.meta.get_label(d)), title=_("Missing Field")) if not employees: frappe.throw( - _("Please select the employees to whom the Salary Structure should be assigned."), + _("Please select at least one employee to assign the Salary Structure."), title=_("No Employees Selected"), ) From bd940381e9757acff0ea14a1d42ec94e1e2f0bf4 Mon Sep 17 00:00:00 2001 From: krantheman Date: Fri, 1 Mar 2024 12:30:09 +0530 Subject: [PATCH 70/79] feat: validate employees with base 0 (cherry picked from commit 29754328c582f27ee45b9b732aeab449e5c13a9e) --- .../bulk_salary_structure_assignment.js | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 7e6b44529a..520343d4b9 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -147,6 +147,8 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { : "Please select From Date." ); + frm.checked_rows_indexes = []; + if (frm.employees_datatable) { frm.employees_datatable.rowmanager.checkMap = []; frm.employees_datatable.options.noDataMessage = no_data_message; @@ -297,6 +299,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { assign_structure(frm) { const rows = frm.employees_datatable.getRows(); const checked_rows_content = []; + const employees_with_base_zero = []; frm.checked_rows_indexes.forEach((idx) => { const row_content = {}; @@ -305,14 +308,41 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { row_content[cell.column.name] = cell.content; }); checked_rows_content.push(row_content); + if (!row_content["base"]) + employees_with_base_zero.push(`${row_content["employee"]}`); }); + if (employees_with_base_zero.length) + return frm.events.validate_base_zero( + frm, + employees_with_base_zero, + checked_rows_content + ); + + frm.events.bulk_assign_structure(frm, checked_rows_content); + }, + + validate_base_zero(frm, employees_with_base_zero, checked_rows_content) { + frappe.warn( + __("Are you sure you want to proceed?"), + __( + "Base amount has not been set for the following employees: {0}", + [employees_with_base_zero.join(", ")] + ), + () => { + frm.events.bulk_assign_structure(frm, checked_rows_content); + }, + __("Continue") + ); + }, + + bulk_assign_structure(frm, employees) { frm .call({ method: "bulk_assign_structure", doc: frm.doc, args: { - employees: checked_rows_content, + employees: employees, }, freeze: true, freeze_message: __("Assigning Salary Structure"), From acb322403ab0306cb6810df20a9da69770b63e05 Mon Sep 17 00:00:00 2001 From: krantheman Date: Fri, 1 Mar 2024 13:53:51 +0530 Subject: [PATCH 71/79] revert: bring back public assign_salary_structure function (cherry picked from commit ef8188eac8e0cd2f4f176e4ea695073ac58e0a50) --- .../salary_structure/salary_structure.py | 87 +++++++++++++++++++ .../salary_structure/test_salary_structure.py | 21 +---- 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/hrms/payroll/doctype/salary_structure/salary_structure.py b/hrms/payroll/doctype/salary_structure/salary_structure.py index 118c2d7a88..0e186d9819 100644 --- a/hrms/payroll/doctype/salary_structure/salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/salary_structure.py @@ -188,6 +188,93 @@ def get_employees(self, **kwargs): return employees + @frappe.whitelist() + def assign_salary_structure( + self, + branch=None, + grade=None, + department=None, + designation=None, + employee=None, + payroll_payable_account=None, + from_date=None, + base=None, + variable=None, + income_tax_slab=None, + ): + employees = self.get_employees( + company=self.company, + grade=grade, + department=department, + designation=designation, + name=employee, + branch=branch, + ) + + if employees: + if len(employees) > 20: + frappe.enqueue( + assign_salary_structure_for_employees, + timeout=3000, + employees=employees, + salary_structure=self, + payroll_payable_account=payroll_payable_account, + from_date=from_date, + base=base, + variable=variable, + income_tax_slab=income_tax_slab, + ) + else: + assign_salary_structure_for_employees( + employees, + self, + payroll_payable_account=payroll_payable_account, + from_date=from_date, + base=base, + variable=variable, + income_tax_slab=income_tax_slab, + ) + else: + frappe.msgprint(_("No Employee Found")) + + +def assign_salary_structure_for_employees( + employees, + salary_structure, + payroll_payable_account=None, + from_date=None, + base=None, + variable=None, + income_tax_slab=None, +): + salary_structure_assignments = [] + existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date) + count = 0 + for employee in employees: + if employee in existing_assignments_for: + continue + count += 1 + + salary_structure_assignment = create_salary_structure_assignment( + employee, + salary_structure.name, + salary_structure.company, + salary_structure.currency, + from_date, + payroll_payable_account, + base, + variable, + income_tax_slab, + ) + salary_structure_assignments.append(salary_structure_assignment) + frappe.publish_progress( + count * 100 / len(set(employees) - set(existing_assignments_for)), + title=_("Assigning Structures..."), + ) + + if salary_structure_assignments: + frappe.msgprint(_("Structures have been assigned successfully")) + def create_salary_structure_assignment( employee, diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py index 344005ba15..cc8c27f7b3 100644 --- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py @@ -18,9 +18,6 @@ make_earning_salary_component, make_employee_salary_slip, ) -from hrms.payroll.doctype.salary_structure.salary_structure import ( - create_salary_structure_assignment as create_assignment, -) from hrms.payroll.doctype.salary_structure.salary_structure import make_salary_slip from hrms.tests.test_utils import create_employee_grade @@ -126,14 +123,8 @@ def test_salary_structures_assignment(self): ("test_assign_stucture@salary.com", salary_structure.name), ) # test structure_assignment - create_assignment( - employee=employee_doc_name, - salary_structure=salary_structure.name, - from_date="2013-01-01", - company=salary_structure.company, - currency=salary_structure.currency, - base=5000, - variable=200, + salary_structure.assign_salary_structure( + employee=employee_doc_name, from_date="2013-01-01", base=5000, variable=200 ) salary_structure_assignment = frappe.get_doc( "Salary Structure Assignment", {"employee": employee_doc_name, "from_date": "2013-01-01"} @@ -150,13 +141,7 @@ def test_employee_grade_defaults(self): employee = make_employee("test_employee_grade@salary.com", company="_Test Company", grade="Lead") # structure assignment should have the default salary structure and base pay - create_assignment( - employee=employee, - salary_structure=salary_structure.name, - company=salary_structure.company, - currency=salary_structure.currency, - from_date=nowdate(), - ) + salary_structure.assign_salary_structure(employee=employee, from_date=nowdate()) structure, base = frappe.db.get_value( "Salary Structure Assignment", {"employee": employee, "salary_structure": salary_structure.name, "from_date": nowdate()}, From c4ba22598921ad65aa552c74db8e815a3a1c81fb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 Mar 2024 17:47:20 +0530 Subject: [PATCH 72/79] chore: fix message (cherry picked from commit 765624057b0ff45a7a0bfb294b5590c3b07aeade) --- .../bulk_salary_structure_assignment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 520343d4b9..8eda081013 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -143,7 +143,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { const columns = frm.events.employees_datatable_columns(); const no_data_message = __( frm.doc.from_date - ? "There are no Employees without a Salary Structure Assignment on this date based on the given filters." + ? "There are no employees without a Salary Structure Assignment on this date based on the given filters." : "Please select From Date." ); From 3a62ba1deb985d9dbf628dd8618d3a6c65404e24 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 Mar 2024 19:21:18 +0530 Subject: [PATCH 73/79] refactor: fix transactional assignment creation - move savepoint inside the for loop - let calling function handle errors and logging - fix logic for publishing progress - gets stuck if a failure occurs (cherry picked from commit 4c8feca45b9a093e6302fc9db19f9616802349ce) --- .../bulk_salary_structure_assignment.py | 37 +++--- .../salary_structure/salary_structure.py | 119 ++++++++++-------- 2 files changed, 85 insertions(+), 71 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index e57f87d7ef..0d8a92067e 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -76,27 +76,32 @@ def bulk_assign_structure(self, employees: list) -> dict: def _bulk_assign_structure(): success, failure = [], [] count = 0 - savepoint = "before_assignments_submission" - frappe.db.savepoint(savepoint) + savepoint = "before_salary_assignment" for d in employees: - assignment = create_salary_structure_assignment( - employee=d["employee"], - salary_structure=self.salary_structure, - company=self.company, - currency=self.currency, - payroll_payable_account=self.payroll_payable_account, - from_date=self.from_date, - base=d["base"], - variable=d["variable"], - income_tax_slab=self.income_tax_slab, - ) - if not assignment: + try: + frappe.db.savepoint(savepoint) + create_salary_structure_assignment( + employee=d["employee"], + salary_structure=self.salary_structure, + company=self.company, + currency=self.currency, + payroll_payable_account=self.payroll_payable_account, + from_date=self.from_date, + base=d["base"], + variable=d["variable"], + income_tax_slab=self.income_tax_slab, + ) + except Exception: frappe.db.rollback(save_point=savepoint) + frappe.log_error( + f"Bulk Assignment - Salary Structure Assignment failed for employee {d['employee']}.", + reference_doctype="Salary Structure Assignment", + ) failure.append(d["employee"]) - continue + else: + success.append(d["employee"]) - success.append(d["employee"]) count += 1 frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) diff --git a/hrms/payroll/doctype/salary_structure/salary_structure.py b/hrms/payroll/doctype/salary_structure/salary_structure.py index 0e186d9819..aec4c67300 100644 --- a/hrms/payroll/doctype/salary_structure/salary_structure.py +++ b/hrms/payroll/doctype/salary_structure/salary_structure.py @@ -247,32 +247,43 @@ def assign_salary_structure_for_employees( variable=None, income_tax_slab=None, ): - salary_structure_assignments = [] + assignments = [] existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date) count = 0 + savepoint = "before_assignment_submission" + for employee in employees: - if employee in existing_assignments_for: - continue - count += 1 - - salary_structure_assignment = create_salary_structure_assignment( - employee, - salary_structure.name, - salary_structure.company, - salary_structure.currency, - from_date, - payroll_payable_account, - base, - variable, - income_tax_slab, - ) - salary_structure_assignments.append(salary_structure_assignment) - frappe.publish_progress( - count * 100 / len(set(employees) - set(existing_assignments_for)), - title=_("Assigning Structures..."), - ) + try: + frappe.db.savepoint(savepoint) + if employee in existing_assignments_for: + continue + + count += 1 + + assignment = create_salary_structure_assignment( + employee, + salary_structure.name, + salary_structure.company, + salary_structure.currency, + from_date, + payroll_payable_account, + base, + variable, + income_tax_slab, + ) + assignments.append(assignment) + frappe.publish_progress( + count * 100 / len(set(employees) - set(existing_assignments_for)), + title=_("Assigning Structures..."), + ) + except Exception: + frappe.db.rollback(save_point=savepoint) + frappe.log_error( + f"Salary Structure Assignment failed for employee {employee}", + reference_doctype="Salary Structure Assignment", + ) - if salary_structure_assignments: + if assignments: frappe.msgprint(_("Structures have been assigned successfully")) @@ -287,44 +298,42 @@ def create_salary_structure_assignment( variable=None, income_tax_slab=None, ): - try: - assignment = frappe.new_doc("Salary Structure Assignment") + assignment = frappe.new_doc("Salary Structure Assignment") + if not payroll_payable_account: + payroll_payable_account = frappe.db.get_value( + "Company", company, "default_payroll_payable_account" + ) if not payroll_payable_account: - payroll_payable_account = frappe.db.get_value( - "Company", company, "default_payroll_payable_account" + frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults')) + + payroll_payable_account_currency = frappe.db.get_value( + "Account", payroll_payable_account, "account_currency" + ) + company_curency = erpnext.get_company_currency(company) + if ( + payroll_payable_account_currency != currency + and payroll_payable_account_currency != company_curency + ): + frappe.throw( + _("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format( + currency, company_curency ) - if not payroll_payable_account: - frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults')) - payroll_payable_account_currency = frappe.db.get_value( - "Account", payroll_payable_account, "account_currency" ) - company_curency = erpnext.get_company_currency(company) - if ( - payroll_payable_account_currency != currency - and payroll_payable_account_currency != company_curency - ): - frappe.throw( - _("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format( - currency, company_curency - ) - ) - assignment.employee = employee - assignment.salary_structure = salary_structure - assignment.company = company - assignment.currency = currency - assignment.payroll_payable_account = payroll_payable_account - assignment.from_date = from_date - assignment.base = base - assignment.variable = variable - assignment.income_tax_slab = income_tax_slab - assignment.save(ignore_permissions=True) - assignment.submit() - return assignment.name - - except Exception: - assignment.log_error(f"Salary Structure Assignment failed for employee {employee}") + assignment.employee = employee + assignment.salary_structure = salary_structure + assignment.company = company + assignment.currency = currency + assignment.payroll_payable_account = payroll_payable_account + assignment.from_date = from_date + assignment.base = base + assignment.variable = variable + assignment.income_tax_slab = income_tax_slab + assignment.save(ignore_permissions=True) + assignment.submit() + + return assignment.name def get_existing_assignments(employees, salary_structure, from_date): From 555f49cb6f0064a234b41b3020707783cc6aa219 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 6 Mar 2024 13:20:36 +0530 Subject: [PATCH 74/79] fix: enqueue function call (cherry picked from commit 50a6c0d07bee287d484a32efe1841a095e861168) --- .../bulk_salary_structure_assignment.py | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 0d8a92067e..dc4e5d7913 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -73,42 +73,48 @@ def validate_fields(self, employees: list): def bulk_assign_structure(self, employees: list) -> dict: self.validate_fields(employees) - def _bulk_assign_structure(): - success, failure = [], [] - count = 0 - savepoint = "before_salary_assignment" + if len(employees) <= 30: + return self._bulk_assign_structure(employees, publish_progress=True) - for d in employees: - try: - frappe.db.savepoint(savepoint) - create_salary_structure_assignment( - employee=d["employee"], - salary_structure=self.salary_structure, - company=self.company, - currency=self.currency, - payroll_payable_account=self.payroll_payable_account, - from_date=self.from_date, - base=d["base"], - variable=d["variable"], - income_tax_slab=self.income_tax_slab, - ) - except Exception: - frappe.db.rollback(save_point=savepoint) - frappe.log_error( - f"Bulk Assignment - Salary Structure Assignment failed for employee {d['employee']}.", - reference_doctype="Salary Structure Assignment", - ) - failure.append(d["employee"]) - else: - success.append(d["employee"]) + frappe.enqueue(self._bulk_assign_structure, timeout=3000, employees=employees) + frappe.msgprint( + _("Creation of Salary Structure Assignments has been queued. It may take a few minutes."), + alert=True, + indicator="blue", + ) - count += 1 - frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) + def _bulk_assign_structure(self, employees: list, publish_progress: bool = False) -> dict: + success, failure = [], [] + count = 0 + savepoint = "before_salary_assignment" - notify_bulk_action_status("Salary Structure Assignment", failure, success) - return {"success": success, "failure": failure} + for d in employees: + try: + frappe.db.savepoint(savepoint) + create_salary_structure_assignment( + employee=d["employee"], + salary_structure=self.salary_structure, + company=self.company, + currency=self.currency, + payroll_payable_account=self.payroll_payable_account, + from_date=self.from_date, + base=d["base"], + variable=d["variable"], + income_tax_slab=self.income_tax_slab, + ) + except Exception: + frappe.db.rollback(save_point=savepoint) + frappe.log_error( + f"Bulk Assignment - Salary Structure Assignment failed for employee {d['employee']}.", + reference_doctype="Salary Structure Assignment", + ) + failure.append(d["employee"]) + else: + success.append(d["employee"]) - if len(employees) <= 20: - return _bulk_assign_structure() + if publish_progress: + count += 1 + frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) - return frappe.enqueue(_bulk_assign_structure, timeout=3000) + notify_bulk_action_status("Salary Structure Assignment", failure, success) + return {"success": success, "failure": failure} From 25b9c30be89a34f028549a0b86fd1229a558b5a3 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 6 Mar 2024 13:25:21 +0530 Subject: [PATCH 75/79] chore: use optional chaining while checking for success response (cherry picked from commit 153774ad99ca56e185a42033754f33774a763cf0) --- .../bulk_salary_structure_assignment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 8eda081013..743e87511f 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -349,7 +349,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }) .then((r) => { // refresh only on complete/partial success - if (r.message.success) frm.refresh(); + if (r.message?.success) frm.refresh(); }); }, }); From cdc638fa6e4de103fff46ce4afe8cb4e45bf3bb5 Mon Sep 17 00:00:00 2001 From: krantheman Date: Wed, 6 Mar 2024 16:00:18 +0530 Subject: [PATCH 76/79] fix: publish_progress in case of queue (cherry picked from commit 02657b8b6dbb14c964914331500940648ae8254c) --- .../bulk_salary_structure_assignment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index dc4e5d7913..0cf82e8503 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -74,7 +74,7 @@ def bulk_assign_structure(self, employees: list) -> dict: self.validate_fields(employees) if len(employees) <= 30: - return self._bulk_assign_structure(employees, publish_progress=True) + return self._bulk_assign_structure(employees) frappe.enqueue(self._bulk_assign_structure, timeout=3000, employees=employees) frappe.msgprint( @@ -83,7 +83,7 @@ def bulk_assign_structure(self, employees: list) -> dict: indicator="blue", ) - def _bulk_assign_structure(self, employees: list, publish_progress: bool = False) -> dict: + def _bulk_assign_structure(self, employees: list, publish_progress: bool = True) -> dict: success, failure = [], [] count = 0 savepoint = "before_salary_assignment" From 8d699f65b740a659d852f325864e0b69d2be56c3 Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 7 Mar 2024 14:44:52 +0530 Subject: [PATCH 77/79] fix: notify status in case of enqueued job (cherry picked from commit 7b1900cfa98c8bbdd02bca718537e5103f96139b) --- .../bulk_salary_structure_assignment.js | 41 ++++++++++------ .../bulk_salary_structure_assignment.py | 18 +++---- hrms/public/js/utils.js | 47 +++++++++++++++++-- 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 743e87511f..910a29b017 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -10,6 +10,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { async refresh(frm) { frm.trigger("set_primary_action"); await frm.trigger("set_payroll_payable_account"); + frm.trigger("handle_realtime"); frm.trigger("get_employees"); }, @@ -96,6 +97,23 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, + handle_realtime(frm) { + frappe.realtime.off("completed_bulk_salary_structure_assignment"); + frappe.realtime.on( + "completed_bulk_salary_structure_assignment", + (message) => { + hrms.notify_bulk_action_status( + "Salary Stucture Assignment", + message.failure, + message.success + ); + + // refresh only on complete/partial success + if (message.success) frm.refresh(); + } + ); + }, + setup_filter_group(frm) { const filter_wrapper = frm.fields_dict.filter_list.$wrapper; filter_wrapper.empty(); @@ -337,19 +355,14 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { }, bulk_assign_structure(frm, employees) { - frm - .call({ - method: "bulk_assign_structure", - doc: frm.doc, - args: { - employees: employees, - }, - freeze: true, - freeze_message: __("Assigning Salary Structure"), - }) - .then((r) => { - // refresh only on complete/partial success - if (r.message?.success) frm.refresh(); - }); + frm.call({ + method: "bulk_assign_structure", + doc: frm.doc, + args: { + employees: employees, + }, + freeze: true, + freeze_message: __("Assigning Salary Structure"), + }); }, }); diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py index 0cf82e8503..9482dd605e 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.py @@ -8,7 +8,6 @@ from frappe.query_builder.functions import Coalesce from frappe.query_builder.terms import SubQuery -from hrms.hr.utils import notify_bulk_action_status from hrms.payroll.doctype.salary_structure.salary_structure import ( create_salary_structure_assignment, ) @@ -70,7 +69,7 @@ def validate_fields(self, employees: list): ) @frappe.whitelist() - def bulk_assign_structure(self, employees: list) -> dict: + def bulk_assign_structure(self, employees: list): self.validate_fields(employees) if len(employees) <= 30: @@ -83,7 +82,7 @@ def bulk_assign_structure(self, employees: list) -> dict: indicator="blue", ) - def _bulk_assign_structure(self, employees: list, publish_progress: bool = True) -> dict: + def _bulk_assign_structure(self, employees: list): success, failure = [], [] count = 0 savepoint = "before_salary_assignment" @@ -112,9 +111,12 @@ def _bulk_assign_structure(self, employees: list, publish_progress: bool = True) else: success.append(d["employee"]) - if publish_progress: - count += 1 - frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) + count += 1 + frappe.publish_progress(count * 100 / len(employees), title=_("Assigning Structure...")) - notify_bulk_action_status("Salary Structure Assignment", failure, success) - return {"success": success, "failure": failure} + frappe.publish_realtime( + "completed_bulk_salary_structure_assignment", + message={"success": success, "failure": failure}, + doctype="Bulk Salary Structure Assignment", + after_commit=True, + ) diff --git a/hrms/public/js/utils.js b/hrms/public/js/utils.js index 560dd9f41a..c5e9ccf35c 100644 --- a/hrms/public/js/utils.js +++ b/hrms/public/js/utils.js @@ -5,10 +5,11 @@ $.extend(hrms, { proceed_save_with_reminders_frequency_change: () => { frappe.ui.hide_open_dialog(); frappe.call({ - method: 'hrms.hr.doctype.hr_settings.hr_settings.set_proceed_with_frequency_change', + method: + "hrms.hr.doctype.hr_settings.hr_settings.set_proceed_with_frequency_change", callback: () => { cur_frm.save(); - } + }, }); }, @@ -20,9 +21,47 @@ $.extend(hrms, { get_current_employee: async (frm) => { const employee = ( - await frappe.db.get_value("Employee", {"user_id": frappe.session.user}, "name") + await frappe.db.get_value( + "Employee", + { user_id: frappe.session.user }, + "name" + ) )?.message?.name; return employee; }, -}) \ No newline at end of file + + notify_bulk_action_status: (doctype, failure, success) => { + let msg = ""; + let title = ""; + let indicator = "green"; + + if (failure.length) { + msg += __("Failed to create/submit {0} for employees:", [doctype]); + msg += " " + frappe.utils.comma_and(failure) + "
"; + msg += __( + "Check {1} for more details", + [doctype, __("Error Log")] + ); + + if (success.length) { + title = __("Partial Success"); + msg += "
"; + } else title = __("Creation Failed"); + } else title = __("Success"); + + if (success.length) { + msg += __("Successfully created {0} for employees:", [doctype]); + msg += " " + frappe.utils.comma_and(success); + } + + if (failure.length) indicator = success ? "orange" : "red"; + + frappe.msgprint({ + message: msg, + indicator: indicator, + title: title, + is_minimizable: true, + }); + }, +}); From e5309302f084672241e5382a55fe1adda21a030d Mon Sep 17 00:00:00 2001 From: krantheman Date: Thu, 7 Mar 2024 15:27:47 +0530 Subject: [PATCH 78/79] chore: improve readability (cherry picked from commit 168adf3229486ef1f063eee61877b5ba24ce3875) --- hrms/public/js/utils.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/hrms/public/js/utils.js b/hrms/public/js/utils.js index c5e9ccf35c..8d11ca17dc 100644 --- a/hrms/public/js/utils.js +++ b/hrms/public/js/utils.js @@ -32,35 +32,36 @@ $.extend(hrms, { }, notify_bulk_action_status: (doctype, failure, success) => { - let msg = ""; - let title = ""; + let message = ""; + let title = __("Success"); let indicator = "green"; if (failure.length) { - msg += __("Failed to create/submit {0} for employees:", [doctype]); - msg += " " + frappe.utils.comma_and(failure) + "
"; - msg += __( + message += __("Failed to create/submit {0} for employees:", [doctype]); + message += " " + frappe.utils.comma_and(failure) + "
"; + message += __( "Check {1} for more details", [doctype, __("Error Log")] ); + title = __("Creation Failed"); + indicator = "red"; if (success.length) { + message += "
"; title = __("Partial Success"); - msg += "
"; - } else title = __("Creation Failed"); - } else title = __("Success"); + indicator = "orange"; + } + } if (success.length) { - msg += __("Successfully created {0} for employees:", [doctype]); - msg += " " + frappe.utils.comma_and(success); + message += __("Successfully created {0} for employees:", [doctype]); + message += " " + frappe.utils.comma_and(success); } - if (failure.length) indicator = success ? "orange" : "red"; - frappe.msgprint({ - message: msg, - indicator: indicator, - title: title, + message, + title, + indicator, is_minimizable: true, }); }, From 9e991f6ac934f33412a9254d4b15950db79c69d1 Mon Sep 17 00:00:00 2001 From: krantheman Date: Mon, 11 Mar 2024 12:05:58 +0530 Subject: [PATCH 79/79] feat: add confirmation dialog while assigning structure (cherry picked from commit ce553ccbc1723d9c1139d759bd5a1913f066a4ee) --- .../bulk_salary_structure_assignment.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js index 910a29b017..dea7a151ff 100644 --- a/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js +++ b/hrms/payroll/doctype/bulk_salary_structure_assignment/bulk_salary_structure_assignment.js @@ -337,7 +337,7 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { checked_rows_content ); - frm.events.bulk_assign_structure(frm, checked_rows_content); + return frm.events.show_confirm_dialog(frm, checked_rows_content); }, validate_base_zero(frm, employees_with_base_zero, checked_rows_content) { @@ -354,6 +354,12 @@ frappe.ui.form.on("Bulk Salary Structure Assignment", { ); }, + show_confirm_dialog(frm, checked_rows_content) { + frappe.confirm(__("Assign Salary Structure to selected employees?"), () => { + frm.events.bulk_assign_structure(frm, checked_rows_content); + }); + }, + bulk_assign_structure(frm, employees) { frm.call({ method: "bulk_assign_structure",