Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4945-dejar-buscar-tag-nombreTemporal
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alexandre Riera 2023-01-20 10:11:52 +01:00
commit 199fabbb1a
17 changed files with 169 additions and 124 deletions

View File

@ -5,20 +5,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2302.01] - 2023-01-12 ## [2304.01] - 2023-02-09
### Added ### Added
- [General](Inicio) Permite recuperar la contraseña -
- [Ticket](Opciones) Subir albarán a Docuware
- [Ticket](Opciones) Enviar correo con PDF de Docuware
- [Artículo](Datos Básicos) Añadido campo Unidades/Caja
### Changed ### Changed
- [Reclamaciones](Descriptor) Cambiado el campo Agencia por Zona -
- [Tickets](Líneas preparadas) Actualizada sección para que sea más visual
### Fixed ### Fixed
- [General] Al utilizar el traductor de Google se descuadraban los iconos -
## [2302.01] - 2023-01-26
### Added
- (General -> Inicio) Permite recuperar la contraseña
- (Tickets -> Opciones) Subir albarán a Docuware
- (Tickets -> Opciones) Enviar correo con PDF de Docuware
- (Artículos -> Datos Básicos) Añadido campo Unidades/Caja
### Changed
- (Reclamaciones -> Descriptor) Cambiado el campo Agencia por Zona
- (Tickets -> Líneas preparadas) Actualizada sección para que sea más visual
### Fixed
- (General) Al utilizar el traductor de Google se descuadraban los iconos
### Removed ### Removed
- [Tickets](Control clientes) Eliminada sección - (Tickets -> Control clientes) Eliminada sección

View File

@ -36,19 +36,26 @@ module.exports = Self => {
JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id
JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T' JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T'
JOIN ( JOIN (
SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated SELECT sub2.thread_id, sub2.type, sub2.updated, sub2.created
FROM osticket.ost_thread_entry ote FROM (
WHERE ote.staff_id AND ote.type = 'R' SELECT ote.thread_id, ote.created, ote.updated, ote.type
GROUP BY ote.thread_id FROM osticket.ost_thread_entry ote
WHERE ote.staff_id
ORDER BY ote.id DESC
LIMIT 10000000000000000000) sub2
GROUP BY sub2.thread_id
) sub ON sub.thread_id = ot2.id ) sub ON sub.thread_id = ot2.id
WHERE ot.isanswered WHERE ot.isanswered
AND ots.state = ? AND ots.id IN (?)
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`; AND sub.type = 'R'
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY);`;
const ticketsId = []; const ticketsId = [];
const statusIdToClose = config.oldStatus.split(',');
con.connect(err => { con.connect(err => {
if (err) throw err; if (err) throw err;
con.query(sql, [config.oldStatus, config.day], con.query(sql, [statusIdToClose, config.day],
(err, results) => { (err, results) => {
if (err) throw err; if (err) throw err;
for (const result of results) for (const result of results)

View File

@ -0,0 +1,5 @@
UPDATE `vn`.`osTicketConfig`
SET oldStatus='1,6'
WHERE id=0;

View File

@ -0,0 +1,6 @@
ALTER TABLE `vn`.`workerTimeControlConfig` ADD teleworkingStart INT NULL COMMENT 'Hora comienzo jornada de los teletrabajdores expresada en segundos';
ALTER TABLE `vn`.`workerTimeControlConfig` ADD teleworkingStartBreakTime INT NULL COMMENT 'Hora comienzo descanso de los teletrabjadores expresada en segundos';
UPDATE `vn`.`workerTimeControlConfig`
SET `teleworkingStart`=28800, `teleworkingStartBreakTime`=32400
WHERE `id`=1;

View File

View File

@ -2667,9 +2667,9 @@ INSERT INTO `vn`.`sectorCollectionSaleGroup` (`sectorCollectionFk`, `saleGroupFk
VALUES VALUES
(1, 1); (1, 1);
INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, `shortWeekBreak`, `longWeekBreak`, `weekScope`, `mailPass`, `mailHost`, `mailSuccessFolder`, `mailErrorFolder`, `mailUser`, `minHoursToBreak`, `breakHours`, `hoursCompleteWeek`, `startNightlyHours`, `endNightlyHours`, `maxTimePerDay`, `breakTime`, `timeToBreakTime`, `dayMaxTime`, `shortWeekDays`, `longWeekDays`) INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, `shortWeekBreak`, `longWeekBreak`, `weekScope`, `mailPass`, `mailHost`, `mailSuccessFolder`, `mailErrorFolder`, `mailUser`, `minHoursToBreak`, `breakHours`, `hoursCompleteWeek`, `startNightlyHours`, `endNightlyHours`, `maxTimePerDay`, `breakTime`, `timeToBreakTime`, `dayMaxTime`, `shortWeekDays`, `longWeekDays`, `teleworkingStart`, `teleworkingStartBreakTime`)
VALUES VALUES
(1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13); (1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13, 28800, 32400);
INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`) INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`)
VALUES VALUES
@ -2745,7 +2745,7 @@ INSERT INTO `vn`.`ticketLog` (`originFk`, userFk, `action`, changedModel, oldIns
INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`) INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
VALUES VALUES
(0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', '1,6', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all');
INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`) INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`)
VALUES VALUES

View File

@ -36,13 +36,13 @@
ng-if="$ctrl.isAgricultural()" ng-if="$ctrl.isAgricultural()"
ng-click="$ctrl.showPdfInvoice()" ng-click="$ctrl.showPdfInvoice()"
translate> translate>
Show agricultural invoice as PDF Show agricultural receipt as PDF
</vn-item> </vn-item>
<vn-item <vn-item
ng-if="$ctrl.isAgricultural()" ng-if="$ctrl.isAgricultural()"
ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplierContact[0].email})" ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplierContact[0].email})"
translate> translate>
Send agricultural invoice as PDF Send agricultural receipt as PDF
</vn-item> </vn-item>
</slot-menu> </slot-menu>
<slot-body> <slot-body>

View File

@ -19,6 +19,6 @@ To book: Contabilizar
Total amount: Total importe Total amount: Total importe
Total net: Total neto Total net: Total neto
Total stems: Total tallos Total stems: Total tallos
Show agricultural invoice as PDF: Ver factura agrícola como PDF Show agricultural receipt as PDF: Ver recibo agrícola como PDF
Send agricultural invoice as PDF: Enviar factura agrícola como PDF Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
New InvoiceIn: Nueva Factura New InvoiceIn: Nueva Factura

View File

@ -32,94 +32,87 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const args = ctx.args; const args = ctx.args;
const $t = ctx.req.__; // $translate
let tx; let tx;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const stmts = []; const stmts = [];
let stmt; let stmt;
try { if (!args.week || !args.year) {
if (!args.week || !args.year) { const from = new Date();
const from = new Date(); const to = new Date();
const to = new Date();
const time = await models.Time.findOne({ const time = await models.Time.findOne({
where: { where: {
dated: {between: [from.setDate(from.getDate() - 10), to.setDate(to.getDate() - 4)]} dated: {between: [from.setDate(from.getDate() - 10), to.setDate(to.getDate() - 4)]}
}, },
order: 'week ASC' order: 'week ASC'
}, myOptions); }, myOptions);
args.week = time.week; args.week = time.week;
args.year = time.year; args.year = time.year;
} }
const started = getStartDateOfWeekNumber(args.week, args.year); const started = getStartDateOfWeekNumber(args.week, args.year);
started.setHours(0, 0, 0, 0); started.setHours(0, 0, 0, 0);
const ended = new Date(started); const ended = new Date(started);
ended.setDate(started.getDate() + 6); ended.setDate(started.getDate() + 6);
ended.setHours(23, 59, 59, 999); ended.setHours(23, 59, 59, 999);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate');
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeBusinessCalculate'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeBusinessCalculate');
if (args.workerId) { if (args.workerId) {
await models.WorkerTimeControl.destroyAll({ await models.WorkerTimeControl.destroyAll({
userFk: args.workerId, userFk: args.workerId,
timed: {between: [started, ended]}, timed: {between: [started, ended]},
isSendMail: true isSendMail: true
}, myOptions); }, myOptions);
const where = { const where = {
workerFk: args.workerId, workerFk: args.workerId,
year: args.year, year: args.year,
week: args.week week: args.week
}; };
await models.WorkerTimeControlMail.updateAll(where, { await models.WorkerTimeControlMail.updateAll(where, {
updated: new Date(), state: 'SENDED' updated: new Date(), state: 'SENDED'
}, myOptions); }, myOptions);
stmt = new ParameterizedSQL( stmt = new ParameterizedSQL(
`CALL vn.timeControl_calculateByUser(?, ?, ?) `CALL vn.timeControl_calculateByUser(?, ?, ?)
`, [args.workerId, started, ended]); `, [args.workerId, started, ended]);
stmts.push(stmt); stmts.push(stmt);
stmt = new ParameterizedSQL( stmt = new ParameterizedSQL(
`CALL vn.timeBusiness_calculateByUser(?, ?, ?) `CALL vn.timeBusiness_calculateByUser(?, ?, ?)
`, [args.workerId, started, ended]); `, [args.workerId, started, ended]);
stmts.push(stmt); stmts.push(stmt);
} else { } else {
await models.WorkerTimeControl.destroyAll({ await models.WorkerTimeControl.destroyAll({
timed: {between: [started, ended]}, timed: {between: [started, ended]},
isSendMail: true isSendMail: true
}, myOptions); }, myOptions);
const where = { const where = {
year: args.year, year: args.year,
week: args.week week: args.week
}; };
await models.WorkerTimeControlMail.updateAll(where, { await models.WorkerTimeControlMail.updateAll(where, {
updated: new Date(), state: 'SENDED' updated: new Date(), state: 'SENDED'
}, myOptions); }, myOptions);
stmt = new ParameterizedSQL(`CALL vn.timeControl_calculateAll(?, ?)`, [started, ended]); stmt = new ParameterizedSQL(`CALL vn.timeControl_calculateAll(?, ?)`, [started, ended]);
stmts.push(stmt); stmts.push(stmt);
stmt = new ParameterizedSQL(`CALL vn.timeBusiness_calculateAll(?, ?)`, [started, ended]); stmt = new ParameterizedSQL(`CALL vn.timeBusiness_calculateAll(?, ?)`, [started, ended]);
stmts.push(stmt); stmts.push(stmt);
} }
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`
SELECT CONCAT(u.name, '@verdnatura.es') receiver, SELECT CONCAT(u.name, '@verdnatura.es') receiver,
u.id workerFk, u.id workerFk,
tb.dated, tb.dated,
@ -154,25 +147,33 @@ module.exports = Self => {
AND w.businessFk AND w.businessFk
ORDER BY u.id, tb.dated ORDER BY u.id, tb.dated
`, [args.workerId]); `, [args.workerId]);
const index = stmts.push(stmt) - 1; const index = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';'); stmts.push('DROP TEMPORARY TABLE tmp.timeControlCalculate');
const days = await conn.executeStmt(sql, myOptions); stmts.push('DROP TEMPORARY TABLE tmp.timeBusinessCalculate');
let previousWorkerFk = days[index][0].workerFk; const sql = ParameterizedSQL.join(stmts, ';');
let previousReceiver = days[index][0].receiver; const days = await conn.executeStmt(sql, myOptions);
const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions); let previousWorkerFk = days[index][0].workerFk;
let previousReceiver = days[index][0].receiver;
for (let day of days[index]) { const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions);
for (let day of days[index]) {
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
workerFk = day.workerFk; workerFk = day.workerFk;
if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null
&& (day.permissionRate ? day.permissionRate : true)) { && (day.permissionRate == null ? true : day.permissionRate)) {
if (day.timeTable == null) { if (day.timeTable == null) {
const timed = new Date(day.dated); const timed = new Date(day.dated);
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(8), timed: timed.setHours(workerTimeControlConfig.teleworkingStart / 3600),
manual: true, manual: true,
direction: 'in', direction: 'in',
isSendMail: true isSendMail: true
@ -181,7 +182,7 @@ module.exports = Self => {
if (day.timeWorkDecimal >= workerTimeControlConfig.timeToBreakTime / 3600) { if (day.timeWorkDecimal >= workerTimeControlConfig.timeToBreakTime / 3600) {
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(9), timed: timed.setHours(workerTimeControlConfig.teleworkingStartBreakTime / 3600),
manual: true, manual: true,
direction: 'middle', direction: 'middle',
isSendMail: true isSendMail: true
@ -189,7 +190,10 @@ module.exports = Self => {
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(9, 20), timed: timed.setHours(
workerTimeControlConfig.teleworkingStartBreakTime / 3600,
workerTimeControlConfig.breakTime / 60
),
manual: true, manual: true,
direction: 'middle', direction: 'middle',
isSendMail: true isSendMail: true
@ -199,7 +203,11 @@ module.exports = Self => {
const [hoursWork, minutesWork, secondsWork] = getTime(day.timeWorkSexagesimal); const [hoursWork, minutesWork, secondsWork] = getTime(day.timeWorkSexagesimal);
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(8 + hoursWork, minutesWork, secondsWork), timed: timed.setHours(
workerTimeControlConfig.teleworkingStart / 3600 + hoursWork,
minutesWork,
secondsWork
),
manual: true, manual: true,
direction: 'out', direction: 'out',
isSendMail: true isSendMail: true
@ -307,7 +315,7 @@ module.exports = Self => {
}, myOptions); }, myOptions);
if (firstWorkerTimeControl) if (firstWorkerTimeControl)
firstWorkerTimeControl.updateAttribute('direction', 'in', myOptions); await firstWorkerTimeControl.updateAttribute('direction', 'in', myOptions);
const lastWorkerTimeControl = await models.WorkerTimeControl.findOne({ const lastWorkerTimeControl = await models.WorkerTimeControl.findOne({
where: { where: {
@ -318,7 +326,7 @@ module.exports = Self => {
}, myOptions); }, myOptions);
if (lastWorkerTimeControl) if (lastWorkerTimeControl)
lastWorkerTimeControl.updateAttribute('direction', 'out', myOptions); await lastWorkerTimeControl.updateAttribute('direction', 'out', myOptions);
} }
} }
@ -339,15 +347,18 @@ module.exports = Self => {
previousWorkerFk = day.workerFk; previousWorkerFk = day.workerFk;
previousReceiver = day.receiver; previousReceiver = day.receiver;
} }
if (tx) {
await tx.commit();
delete myOptions.transaction;
}
} catch (e) {
if (tx) await tx.rollback();
throw e;
} }
if (tx) await tx.commit();
return true;
} catch (e) {
if (tx) await tx.rollback();
throw e;
} }
return true;
}; };
function getStartDateOfWeekNumber(week, year) { function getStartDateOfWeekNumber(week, year) {

View File

@ -10,7 +10,6 @@ describe('workerTimeControl sendMail()', () => {
const ctx = {req: activeCtx, args: {}}; const ctx = {req: activeCtx, args: {}};
it('should fill time control of a worker without records in Journey and with rest', async() => { it('should fill time control of a worker without records in Journey and with rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
try { try {
@ -35,7 +34,6 @@ describe('workerTimeControl sendMail()', () => {
}); });
it('should fill time control of a worker without records in Journey and without rest', async() => { it('should fill time control of a worker without records in Journey and without rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const workdayOf20Hours = 3; const workdayOf20Hours = 3;
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
@ -63,7 +61,6 @@ describe('workerTimeControl sendMail()', () => {
}); });
it('should fill time control of a worker with records in Journey and with rest', async() => { it('should fill time control of a worker with records in Journey and with rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
try { try {
@ -95,7 +92,6 @@ describe('workerTimeControl sendMail()', () => {
}); });
it('should fill time control of a worker with records in Journey and without rest', async() => { it('should fill time control of a worker with records in Journey and without rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
try { try {

View File

@ -2,7 +2,7 @@ const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('weeklyHourRecordEmail', { Self.remoteMethodCtx('weeklyHourRecordEmail', {
description: 'Sends the buyer waste email', description: 'Sends the weekly hour record',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
{ {

View File

@ -11,8 +11,17 @@
"id": true, "id": true,
"type": "number" "type": "number"
}, },
"breakTime": {
"type": "number"
},
"timeToBreakTime": { "timeToBreakTime": {
"type": "number" "type": "number"
},
"teleworkingStart": {
"type": "number"
},
"teleworkingStartBreakTime": {
"type": "number"
} }
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "9.0.0", "version": "230401",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",

View File

@ -1,5 +1,5 @@
subject: Your agricultural invoice subject: Your agricultural receipt
title: Your agricultural invoice title: Your agricultural receipt
dear: Dear supplier dear: Dear supplier
description: Attached you can find agricultural receipt generated from your last deliveries. Please return a signed and stamped copy to our administration department. description: Attached you can find agricultural receipt generated from your last deliveries. Please return a signed and stamped copy to our administration department.
conclusion: Thanks for your attention! conclusion: Thanks for your attention!

View File

@ -1,5 +1,5 @@
subject: Tu factura agrícola subject: Tu recibo agrícola
title: Tu factura agrícola title: Tu recibo agrícola
dear: Estimado proveedor dear: Estimado proveedor
description: Adjunto puede encontrar recibo agrícola generado de sus últimas entregas. Por favor, devuelva una copia firmada y sellada a nuestro de departamento de administración. description: Adjunto puede encontrar recibo agrícola generado de sus últimas entregas. Por favor, devuelva una copia firmada y sellada a nuestro de departamento de administración.
conclusion: ¡Gracias por tu atención! conclusion: ¡Gracias por tu atención!

View File

@ -1,5 +1,5 @@
subject: Votre facture agricole subject: Votre reçu agricole
title: Votre facture agricole title: Votre reçu agricole
dear: Cher Fournisseur dear: Cher Fournisseur
description: Vous trouverez en pièce jointe le reçu agricole généré à partir de vos dernières livraisons. Veuillez retourner une copie signée et tamponnée à notre service administratif. description: Vous trouverez en pièce jointe le reçu agricole généré à partir de vos dernières livraisons. Veuillez retourner une copie signée et tamponnée à notre service administratif.
conclusion: Merci pour votre attention! conclusion: Merci pour votre attention!

View File

@ -1,5 +1,5 @@
subject: A sua fatura agrícola subject: A sua recibo agrícola
title: A sua fatura agrícola title: A sua recibo agrícola
dear: Caro Fornecedor dear: Caro Fornecedor
description: Em anexo encontra-se o recibo agrícola gerado a partir das suas últimas entregas. Por favor, devolva uma cópia assinada e carimbada ao nosso departamento de administração. description: Em anexo encontra-se o recibo agrícola gerado a partir das suas últimas entregas. Por favor, devolva uma cópia assinada e carimbada ao nosso departamento de administração.
conclusion: Obrigado pela atenção. conclusion: Obrigado pela atenção.