vnext
Paul Schneider 7 years ago
parent 3fc9e9be84
commit 5189be288b
144 changed files with 10574 additions and 1527 deletions

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace Yavsc.Billing
@ -7,5 +8,16 @@ namespace Yavsc.Billing
List<IBillItem> GetBillItems();
long Id { get; set; }
string ActivityCode { get; set; }
string PerformerId { get; set; }
string ClientId { get; set; }
/// <summary>
/// Date de validation de la demande par le client
/// </summary>
/// <returns></returns>
DateTime? ValidationDate { get; }
}
}

@ -1,6 +1,7 @@
namespace Yavsc
{
using System.Collections.Generic;
public interface IEstimate
{
List<string> AttachedFiles { get; set; }

@ -110,6 +110,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="project.json" />
<Content Include="Workflow\IQuery.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

@ -19,8 +19,11 @@ namespace Yavsc.ApiControllers
using Models.Messaging;
using ViewModels.Auth;
[Route("api/pdfestimate"), Authorize]
public class PdfEstimateController : Controller
using Yavsc.ViewComponents;
using Newtonsoft.Json;
[Route("api/bill"), Authorize]
public class BillingController : Controller
{
ApplicationDbContext dbContext;
private IStringLocalizer _localizer;
@ -28,59 +31,64 @@ namespace Yavsc.ApiControllers
private IGoogleCloudMessageSender _GCMSender;
private IAuthorizationService authorizationService;
private ILogger logger;
private IBillingService billingService;
public PdfEstimateController(
public BillingController(
IAuthorizationService authorizationService,
ILoggerFactory loggerFactory,
IStringLocalizer<Yavsc.Resources.YavscLocalisation> SR,
ApplicationDbContext context,
IOptions<GoogleAuthSettings> googleSettings,
IGoogleCloudMessageSender GCMSender
IGoogleCloudMessageSender GCMSender,
IBillingService billingService
)
{
_googleSettings=googleSettings.Value;
this.authorizationService = authorizationService;
dbContext = context;
logger = loggerFactory.CreateLogger<PdfEstimateController>();
logger = loggerFactory.CreateLogger<BillingController>();
this._localizer = SR;
_GCMSender=GCMSender;
this.billingService=billingService;
}
[HttpGet("get/{id}", Name = "Get"), Authorize]
public async Task<IActionResult> Get(long id)
[HttpGet("pdf/facture-{billingCode}-{id}.pdf"), Authorize]
public async Task<IActionResult> GetPdf(string billingCode, long id)
{
var estimate = dbContext.Estimates.Include(
e=>e.Query
).FirstOrDefault(e=>e.Id == id);
if (!await authorizationService.AuthorizeAsync(User, estimate, new ViewRequirement()))
var bill = await billingService.GetBillAsync(billingCode, id);
if (!await authorizationService.AuthorizeAsync(User, bill, new ViewRequirement()))
{
return new ChallengeResult();
}
var filename = $"estimate-{id}.pdf";
var filename = $"facture-{billingCode}-{id}.pdf";
FileInfo fi = new FileInfo(Path.Combine(Startup.UserBillsDirName, filename));
if (!fi.Exists) return Ok(new { Error = "Not generated" });
return File(fi.OpenRead(), "application/x-pdf", filename); ;
}
[HttpGet("estimate-{id}.tex", Name = "GetTex"), Authorize]
public async Task<IActionResult> GetTex(long id)
[HttpGet("tex/{billingCode}-{id}.tex"), Authorize]
public async Task<IActionResult> GetTex(string billingCode, long id)
{
var estimate = dbContext.Estimates.Include(
e=>e.Query
).FirstOrDefault(e=>e.Id == id);
if (!await authorizationService.AuthorizeAsync(User, estimate, new ViewRequirement()))
var bill = await billingService.GetBillAsync(billingCode, id);
if (bill==null) return this.HttpNotFound();
logger.LogVerbose(JsonConvert.SerializeObject(bill));
if (!await authorizationService.AuthorizeAsync(User, bill, new ViewRequirement()))
{
return new ChallengeResult();
}
Response.ContentType = "text/x-tex";
return ViewComponent("Estimate",new object[] { id, "LaTeX" });
return ViewComponent("Bill",new object[] {  billingCode, bill , OutputFormat.LaTeX, true, false });
}
[HttpPost("gen/{id}")]
public async Task<IActionResult> GeneratePdf(long id)
[HttpPost("genpdf/{billingCode}/{id}")]
public async Task<IActionResult> GeneratePdf(string billingCode, long id)
{
var estimate = dbContext.Estimates.Include(
e=>e.Query
@ -89,12 +97,12 @@ namespace Yavsc.ApiControllers
{
return new ChallengeResult();
}
return ViewComponent("Estimate",new object[] { id, "Pdf" } );
return ViewComponent("Bill",new object[] { billingCode, id, OutputFormat.Pdf } );
}
[HttpPost("prosign/{id}")]
public async Task<IActionResult> ProSign(long id)
[HttpPost("prosign/{billingCode}/{id}")]
public async Task<IActionResult> ProSign(string billingCode, long id)
{
var estimate = dbContext.Estimates.
Include(e=>e.Client).Include(e=>e.Client.Devices)
@ -108,7 +116,7 @@ namespace Yavsc.ApiControllers
}
if (Request.Form.Files.Count!=1)
return new BadRequestResult();
User.ReceiveProSignature(id,Request.Form.Files[0],"pro");
User.ReceiveProSignature(billingCode,id,Request.Form.Files[0],"pro");
estimate.ProviderValidationDate = DateTime.Now;
dbContext.SaveChanges(User.GetUserId());
// Notify the client
@ -125,8 +133,8 @@ namespace Yavsc.ApiControllers
return Ok (new { ProviderValidationDate = estimate.ProviderValidationDate, GCMSent = gcmSent });
}
[HttpGet("prosign/{id}")]
public async Task<IActionResult> GetProSign(long id)
[HttpGet("prosign/{billingCode}/{id}")]
public async Task<IActionResult> GetProSign(string billingCode, long id)
{
// For authorization purpose
var estimate = dbContext.Estimates.FirstOrDefault(e=>e.Id == id);
@ -135,14 +143,14 @@ namespace Yavsc.ApiControllers
return new ChallengeResult();
}
var filename = FileSystemHelpers.SignFileNameFormat("pro",id);
var filename = FileSystemHelpers.SignFileNameFormat("pro",billingCode,id);
FileInfo fi = new FileInfo(Path.Combine(Startup.UserBillsDirName, filename));
if (!fi.Exists) return HttpNotFound(new { Error = "Professional signature not found" });
return File(fi.OpenRead(), "application/x-pdf", filename); ;
}
[HttpPost("clisign/{id}")]
public async Task<IActionResult> CliSign(long id)
[HttpPost("clisign/{billingCode}/{id}")]
public async Task<IActionResult> CliSign(string billingCode, long id)
{
var uid = User.GetUserId();
var estimate = dbContext.Estimates.Include( e=>e.Query
@ -154,14 +162,14 @@ namespace Yavsc.ApiControllers
}
if (Request.Form.Files.Count!=1)
return new BadRequestResult();
User.ReceiveProSignature(id,Request.Form.Files[0],"cli");
User.ReceiveProSignature(billingCode,id,Request.Form.Files[0],"cli");
estimate.ClientValidationDate = DateTime.Now;
dbContext.SaveChanges(User.GetUserId());
return Ok (new { ClientValidationDate = estimate.ClientValidationDate });
}
[HttpGet("clisign/{id}")]
public async Task<IActionResult> GetCliSign(long id)
[HttpGet("clisign/{billingCode}/{id}")]
public async Task<IActionResult> GetCliSign(string billingCode, long id)
{
// For authorization purpose
var estimate = dbContext.Estimates.FirstOrDefault(e=>e.Id == id);
@ -170,7 +178,7 @@ namespace Yavsc.ApiControllers
return new ChallengeResult();
}
var filename = FileSystemHelpers.SignFileNameFormat("pro",id);
var filename = FileSystemHelpers.SignFileNameFormat("pro",billingCode,id);
FileInfo fi = new FileInfo(Path.Combine(Startup.UserBillsDirName, filename));
if (!fi.Exists) return HttpNotFound(new { Error = "Professional signature not found" });
return File(fi.OpenRead(), "application/x-pdf", filename); ;

@ -39,7 +39,7 @@ namespace Yavsc.Controllers
var uid = User.GetUserId();
var now = DateTime.Now;
var result = _context.Commands.Include(c => c.Location).
var result = _context.RdvQueries.Include(c => c.Location).
Include(c => c.Client).Where(c => c.PerformerId == uid && c.Id < maxId && c.EventDate > now
&& c.ValidationDate == null).
Select(c => new RdvQueryProviderInfo
@ -71,7 +71,7 @@ namespace Yavsc.Controllers
}
var uid = User.GetUserId();
RdvQuery bookQuery = _context.Commands.Where(c => c.ClientId == uid || c.PerformerId == uid).Single(m => m.Id == id);
RdvQuery bookQuery = _context.RdvQueries.Where(c => c.ClientId == uid || c.PerformerId == uid).Single(m => m.Id == id);
if (bookQuery == null)
{
@ -133,7 +133,7 @@ namespace Yavsc.Controllers
ModelState.AddModelError("ClientId", "You must be the client at creating a book query");
return new BadRequestObjectResult(ModelState);
}
_context.Commands.Add(bookQuery);
_context.RdvQueries.Add(bookQuery);
try
{
_context.SaveChanges(User.GetUserId());
@ -162,7 +162,7 @@ namespace Yavsc.Controllers
return HttpBadRequest(ModelState);
}
var uid = User.GetUserId();
RdvQuery bookQuery = _context.Commands.Single(m => m.Id == id);
RdvQuery bookQuery = _context.RdvQueries.Single(m => m.Id == id);
if (bookQuery == null)
{
@ -170,7 +170,7 @@ namespace Yavsc.Controllers
}
if (bookQuery.ClientId != uid) return HttpNotFound();
_context.Commands.Remove(bookQuery);
_context.RdvQueries.Remove(bookQuery);
_context.SaveChanges(User.GetUserId());
return Ok(bookQuery);
@ -187,7 +187,7 @@ namespace Yavsc.Controllers
private bool BookQueryExists(long id)
{
return _context.Commands.Count(e => e.Id == id) > 0;
return _context.RdvQueries.Count(e => e.Id == id) > 0;
}
}
}

@ -0,0 +1,19 @@
using System;
namespace Yavsc.Attributes
{
public class ActivityBillingAttribute : Attribute
{
public string BillingCode { get; private set; }
/// <summary>
/// Identifie une entité de facturation
/// </summary>
/// <param name="billingCode">Code de facturation,
/// Il doit avoir une valeur unique par usage.
/// </param>
public ActivityBillingAttribute(string billingCode) {
BillingCode = billingCode;
}
}
}

@ -3,10 +3,10 @@ using Microsoft.AspNet.Authorization;
namespace Yavsc.ViewModels.Auth.Handlers
{
using Models.Workflow;
public class CommandEditHandler : AuthorizationHandler<EditRequirement, RdvQuery>
using Billing;
public class BillEditHandler : AuthorizationHandler<EditRequirement, IBillable>
{
protected override void Handle(AuthorizationContext context, EditRequirement requirement, RdvQuery resource)
protected override void Handle(AuthorizationContext context, EditRequirement requirement, IBillable resource)
{
if (context.User.IsInRole("FrontOffice"))
context.Succeed(requirement);

@ -3,10 +3,11 @@ using Microsoft.AspNet.Authorization;
namespace Yavsc.ViewModels.Auth.Handlers
{
using Models.Workflow;
public class CommandViewHandler : AuthorizationHandler<ViewRequirement, RdvQuery>
using Billing;
public class BillViewHandler : AuthorizationHandler<ViewRequirement, IBillable>
{
protected override void Handle(AuthorizationContext context, ViewRequirement requirement, RdvQuery resource)
protected override void Handle(AuthorizationContext context, ViewRequirement requirement, IBillable resource)
{
if (context.User.IsInRole("FrontOffice"))
context.Succeed(requirement);

@ -10,20 +10,25 @@ namespace Yavsc.Controllers
using Microsoft.Extensions.Logging;
using Models;
using Models.Workflow;
using Yavsc.Exceptions;
using Yavsc.ViewModels.Workflow;
using Yavsc;
using Yavsc.Services;
using System.Threading.Tasks;
using Newtonsoft.Json;
[Authorize]
public class DoController : Controller
{
private ApplicationDbContext _context;
ILogger _logger;
public DoController(ApplicationDbContext context,ILogger<DoController> logger)
private ApplicationDbContext dbContext;
ILogger logger;
IBillingService billing;
public DoController(
ApplicationDbContext context,
IBillingService billing,
ILogger<DoController> logger)
{
_context = context;
_logger = logger;
dbContext = context;
this.billing = billing;
this.logger = logger;
}
// GET: /Do/Index
@ -33,14 +38,14 @@ namespace Yavsc.Controllers
if (id == null)
id = User.GetUserId();
var userActivities = _context.UserActivities.Include(u => u.Does)
var userActivities = dbContext.UserActivities.Include(u => u.Does)
.Include(u => u.User).Where(u=> u.UserId == id)
.OrderByDescending(u => u.Weight);
return View(userActivities.ToList());
}
// GET: Do/Details/5
public IActionResult Details(string id, string activityCode)
public async Task<IActionResult> Details(string id, string activityCode)
{
if (id == null || activityCode == null)
@ -48,29 +53,23 @@ namespace Yavsc.Controllers
return HttpNotFound();
}
UserActivity userActivity = _context.UserActivities.Include(m=>m.Does)
UserActivity userActivity = dbContext.UserActivities.Include(m=>m.Does)
.Include(m=>m.User).Single(m => m.DoesCode == activityCode && m.UserId == id);
if (userActivity == null)
{
return HttpNotFound();
}
bool hasConfigurableSettings = (userActivity.Does.SettingsClassName != null);
if (hasConfigurableSettings) {
ViewBag.ProfileType = Startup.ProfileTypes.Single(t=>t.FullName==userActivity.Does.SettingsClassName);
var dbset = (IQueryable<ISpecializationSettings>) _context.GetDbSet(userActivity.Does.SettingsClassName);
if (dbset == null) throw new InvalidWorkflowModelException($"pas de db set pour {userActivity.Does.SettingsClassName}, vous avez peut-être besoin de décorer votre propriété avec l'attribut [ActivitySettings]");
return View(new UserActivityViewModel {
var settings = await billing.GetPerformerSettingsAsync(activityCode,id);
ViewBag.ProfileType = Startup.ProfileTypes.Single(t=>t.FullName==userActivity.Does.SettingsClassName);
var gift = new UserActivityViewModel {
Declaration = userActivity,
HasSettings = dbset.Any(ua=>ua.UserId==id),
Settings = settings,
NeedsSettings = hasConfigurableSettings
} );
}
return View(new UserActivityViewModel {
Declaration = userActivity,
HasSettings = false,
NeedsSettings = hasConfigurableSettings
} );
};
logger.LogInformation(JsonConvert.SerializeObject(gift.Settings));
return View (gift);
}
// GET: Do/Create
@ -80,9 +79,9 @@ namespace Yavsc.Controllers
if (userId==null)
userId = User.GetUserId();
var model = new UserActivity { UserId = userId };
ViewBag.DoesCode = new SelectList(_context.Activities, "Code", "Name");
ViewBag.DoesCode = new SelectList(dbContext.Activities, "Code", "Name");
//ViewData["UserId"] = userId;
ViewBag.UserId = new SelectList(_context.Performers.Include(p=>p.Performer), "PerformerId", "Performer", userId);
ViewBag.UserId = new SelectList(dbContext.Performers.Include(p=>p.Performer), "PerformerId", "Performer", userId);
return View(model);
}
@ -98,12 +97,12 @@ namespace Yavsc.Controllers
if (userActivity.UserId == null) userActivity.UserId = uid;
if (ModelState.IsValid)
{
_context.UserActivities.Add(userActivity);
_context.SaveChanges(User.GetUserId());
dbContext.UserActivities.Add(userActivity);
dbContext.SaveChanges(User.GetUserId());
return RedirectToAction("Index");
}
ViewBag.DoesCode = new SelectList(_context.Activities, "Code", "Name", userActivity.DoesCode);
ViewBag.UserId = new SelectList(_context.Performers.Include(p=>p.Performer), "PerformerId", "User", userActivity.UserId);
ViewBag.DoesCode = new SelectList(dbContext.Activities, "Code", "Name", userActivity.DoesCode);
ViewBag.UserId = new SelectList(dbContext.Performers.Include(p=>p.Performer), "PerformerId", "User", userActivity.UserId);
return View(userActivity);
}
@ -116,7 +115,7 @@ namespace Yavsc.Controllers
return HttpNotFound();
}
UserActivity userActivity = _context.UserActivities.Include(
UserActivity userActivity = dbContext.UserActivities.Include(
u=>u.Does
).Include(
u=>u.User
@ -125,8 +124,8 @@ namespace Yavsc.Controllers
{
return HttpNotFound();
}
ViewData["DoesCode"] = new SelectList(_context.Activities, "Code", "Does", userActivity.DoesCode);
ViewData["UserId"] = new SelectList(_context.Performers, "PerformerId", "User", userActivity.UserId);
ViewData["DoesCode"] = new SelectList(dbContext.Activities, "Code", "Does", userActivity.DoesCode);
ViewData["UserId"] = new SelectList(dbContext.Performers, "PerformerId", "User", userActivity.UserId);
return View(userActivity);
}
@ -140,12 +139,12 @@ namespace Yavsc.Controllers
ModelState.AddModelError("User","You're not admin.");
if (ModelState.IsValid)
{
_context.Update(userActivity);
_context.SaveChanges(User.GetUserId());
dbContext.Update(userActivity);
dbContext.SaveChanges(User.GetUserId());
return RedirectToAction("Index");
}
ViewData["DoesCode"] = new SelectList(_context.Activities, "Code", "Does", userActivity.DoesCode);
ViewData["UserId"] = new SelectList(_context.Performers, "PerformerId", "User", userActivity.UserId);
ViewData["DoesCode"] = new SelectList(dbContext.Activities, "Code", "Does", userActivity.DoesCode);
ViewData["UserId"] = new SelectList(dbContext.Performers, "PerformerId", "User", userActivity.UserId);
return View(userActivity);
}
@ -158,7 +157,7 @@ namespace Yavsc.Controllers
return HttpNotFound();
}
UserActivity userActivity = _context.UserActivities.Single(m => m.UserId == id && m.DoesCode == activityCode);
UserActivity userActivity = dbContext.UserActivities.Single(m => m.UserId == id && m.DoesCode == activityCode);
if (userActivity == null)
{
@ -182,8 +181,8 @@ namespace Yavsc.Controllers
ModelState.AddModelError("User","You're not admin.");
return RedirectToAction("Index");
}
_context.UserActivities.Remove(userActivity);
_context.SaveChanges(User.GetUserId());
dbContext.UserActivities.Remove(userActivity);
dbContext.SaveChanges(User.GetUserId());
return RedirectToAction("Index");
}
}

@ -1,13 +1,14 @@
using Yavsc.Controllers.Generic;
using Yavsc.Models;
using Yavsc.Models.Workflow.Profiles;
using Yavsc.Services;
namespace Yavsc.Controllers
{
public class FormationSettingsController : SettingsController<FormationSettings>
{
public FormationSettingsController(ApplicationDbContext context) : base(context)
public FormationSettingsController(ApplicationDbContext context, IBillingService billing) : base(context, billing)
{
}

@ -39,9 +39,9 @@ namespace Yavsc.Controllers
var model = new FrontOfficeIndexViewModel
{
EstimateToProduceCount = _context.Commands.Where(c => c.PerformerId == uid && c.EventDate > now
EstimateToProduceCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now
&& c.ValidationDate == null && !_context.Estimates.Any(e => (e.CommandId == c.Id && e.ProviderValidationDate != null))).Count(),
EstimateToSignAsProCount = _context.Commands.Where(c => (c.PerformerId == uid && c.EventDate > now
EstimateToSignAsProCount = _context.RdvQueries.Where(c => (c.PerformerId == uid && c.EventDate > now
&& c.ValidationDate == null && _context.Estimates.Any(e => (e.CommandId == c.Id && e.ProviderValidationDate != null)))).Count(),
EstimateToSignAsCliCount = _context.Estimates.Where(e => e.ClientId == uid && e.ClientValidationDate == null).Count(),
BillToSignAsProCount = 0,

@ -6,23 +6,29 @@ using Microsoft.Data.Entity;
namespace Yavsc.Controllers.Generic
{
using Exceptions;
using Models;
using Yavsc.Services;
[Authorize]
public abstract class SettingsController<TSettings> : Controller where TSettings : class, ISpecializationSettings, new()
{
protected ApplicationDbContext _context;
private object dbSet;
IBillingService billing;
DbSet<TSettings> dbSet=null;
protected string activityCode=null;
protected DbSet<TSettings> Settings { get {
return (DbSet<TSettings>) dbSet;
if (dbSet == null) Task.Run( async () => {
dbSet = (DbSet<TSettings>) await billing.GetPerformersSettingsAsync(activityCode);
});
return dbSet;
} }
public SettingsController(ApplicationDbContext context)
public SettingsController(ApplicationDbContext context, IBillingService billing)
{
_context = context;
dbSet=_context.GetDbSet<TSettings>();
if (dbSet==null) throw new InvalidWorkflowModelException(this.GetType().Name);
this.billing = billing;
}
public async Task<IActionResult> Index()

@ -2,6 +2,7 @@ using Yavsc.Models;
using Yavsc.Models.Haircut;
using Microsoft.AspNet.Authorization;
using Yavsc.Controllers.Generic;
using Yavsc.Services;
namespace Yavsc.Controllers
{
@ -9,7 +10,7 @@ namespace Yavsc.Controllers
public class BrusherProfileController : SettingsController<BrusherProfile>
{
public BrusherProfileController(ApplicationDbContext context) : base(context)
public BrusherProfileController(ApplicationDbContext context, IBillingService billing) : base(context, billing)
{
}

@ -1,7 +1,5 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Data.Entity;
using Yavsc.Models;
using Yavsc.Models.Relationship;

@ -184,13 +184,13 @@ namespace Yavsc.Helpers
dir, xsmallname), ImageFormat.Png);
}
}
public static Func<string,long,string>
SignFileNameFormat = new Func<string,long,string> ((signType,estimateId) => $"estimate-{signType}sign-{estimateId}.png");
public static Func<string,string,long,string>
SignFileNameFormat = new Func<string,string,long,string> ((signType,billingCode,estimateId) => $"sign-{billingCode}-{signType}-{estimateId}.png");
public static FileRecievedInfo ReceiveProSignature(this ClaimsPrincipal user, long estimateId, IFormFile formFile, string signtype)
public static FileRecievedInfo ReceiveProSignature(this ClaimsPrincipal user, string billingCode, long estimateId, IFormFile formFile, string signtype)
{
var item = new FileRecievedInfo();
item.FileName = SignFileNameFormat("pro",estimateId);
item.FileName = SignFileNameFormat("pro",billingCode,estimateId);
var destFileName = Path.Combine(Startup.SiteSetup.UserFiles.Bills, item.FileName);
var fi = new FileInfo(destFileName);

@ -170,5 +170,8 @@ namespace Yavsc.Helpers
};
}
public static bool IsSuccess(PaymentInfo info) {
return info.DbContent.State == PayPal.PayPalAPIInterfaceService.Model.PaymentStatusCodeType.COMPLETED.ToString();
}
}
}

@ -23,6 +23,9 @@ namespace Yavsc.Helpers
}
public static TeXString operator+ (TeXString a, TeXString b) {
return new TeXString(a.ToString()+b.ToString());
}
}
public class Replacement

@ -6,15 +6,10 @@ default: pushInPre
watch:
ASPNET_ENV=Development dnx-watch web --configuration=Debug
cleanRelease:
clean:
rm -rf bin/Release
cleanoutput:
rm -rf bin/output
clean: cleanoutput cleanRelease
gulp clean
bin/Release:
dnu build --configuration=Release
@ -31,9 +26,13 @@ pushInPre: bin/output/wwwroot/version
ssh root@localhost sync
ssh root@localhost systemctl start kestrel-pre
push: bin/output/wwwroot/version
pushInProd: bin/output/wwwroot/version
ssh root@localhost systemctl stop kestrel
ssh root@localhost rm -rf $(PRODDESTDIR)/approot
(cd bin/output && rsync -ravu ./ root@localhost:$(PRODDESTDIR) >/dev/null)
ssh root@localhost sync
ssh root@localhost systemctl start kestrel
status:
git status -s --porcelain

@ -60,21 +60,7 @@ namespace Yavsc.Models
}
}
public DbSet<TSettings> GetDbSet<TSettings>() where TSettings : class, ISpecializationSettings
{
return (DbSet<TSettings>) GetDbSet(typeof(TSettings).FullName);
}
public IQueryable<ISpecializationSettings> GetDbSet(string settingsClassName)
{
var dbSetPropInfo = Startup.GetUserSettingPropertyInfo(settingsClassName);
if (dbSetPropInfo == null) return null;
// var settingType = dbSetPropInfo.PropertyType;
// var dbSetType = typeof(DbSet<>).MakeGenericType(new Type[] { settingType } );
// avec une info method Remove et Update, ça le ferait ...
return (IQueryable<ISpecializationSettings>) dbSetPropInfo.GetValue(this);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(Startup.ConnectionString);
@ -107,23 +93,18 @@ namespace Yavsc.Models
/// <returns></returns>
public DbSet<CircleMember> CircleMembers { get; set; }
/// <summary>
/// Commands, from an user, to a performer
/// (A performer is an user who's actived a main activity
/// on his profile).
/// </summary>
/// <returns></returns>
public DbSet<RdvQuery> Commands { get; set; }
/// <summary>
/// Special commands, talking about
/// a given place and date.
/// </summary>
/// <returns></returns>
public DbSet<RdvQuery> RdvQueries { get; set; }
public DbSet<HairCutQuery> HairCutQueries { get; set; }
public DbSet<HairPrestation> HairPrestation { get; set; }
public DbSet<HairMultiCutQuery> HairMultiCutQueries { get; set; }
public DbSet<PerformerProfile> Performers { get; set; }
public DbSet<Estimate> Estimates { get; set; }
public DbSet<AccountBalance> BankStatus { get; set; }
public DbSet<BalanceImpact> BankBook { get; set; }

@ -82,5 +82,6 @@ namespace Yavsc.Models.Billing
[ForeignKey("PaymentId"), Display(Name = "Acquittement de la facture")]
public virtual PayPalPayment Regularisation { get; set; }
}
}

@ -1,17 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Yavsc.Models.Billing
{
public partial class satisfaction
{
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long _id { get; set; }
public string comnt { get; set; }
public int? rate { get; set; }
[ForeignKey("Skill.Id")]
public long? userskillid { get; set; }
}
}

@ -1,13 +0,0 @@
namespace Yavsc.Models.Billing
{
public partial class writtings
{
public long _id { get; set; }
public int? count { get; set; }
public string description { get; set; }
public long estimid { get; set; }
public string productid { get; set; }
public decimal? ucost { get; set; }
}
}

@ -1,9 +0,0 @@
namespace Yavsc.Models.Billing
{
public partial class wrtags
{
public long wrid { get; set; }
public long tagid { get; set; }
}
}

@ -16,7 +16,7 @@ namespace Yavsc.Models.Relationship
[ForeignKey("OwnerId"),JsonIgnore,NotMapped]
public virtual ApplicationUser Owner { get; set; }
[InverseProperty("Circle")]
[InverseProperty("Circle"),JsonIgnore]
public virtual List<CircleMember> Members { get; set; }
}
}

@ -6,6 +6,7 @@ namespace Yavsc.Models.Workflow
{
using System;
using Models.Relationship;
using Newtonsoft.Json;
using Yavsc.Workflow;
public class PerformerProfile : IPerformerProfile {
@ -16,7 +17,7 @@ namespace Yavsc.Models.Workflow
public virtual ApplicationUser Performer { get; set; }
[InverseProperty("User")]
[Display(Name="Activity")]
[Display(Name="Activity"), JsonIgnore]
public virtual List<UserActivity> Activity { get; set; }
[Required,StringLength(14),Display(Name="SIREN"),

@ -0,0 +1,62 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Yavsc.Billing;
using Yavsc.Models;
namespace Yavsc.Services
{
public class BillingService : IBillingService
{
public ApplicationDbContext DbContext { get; private set; }
private ILogger logger;
public BillingService(ILoggerFactory loggerFactory, ApplicationDbContext dbContext)
{
logger = loggerFactory.CreateLogger<BillingService>();
DbContext = dbContext;
}
public async Task<IQueryable<ISpecializationSettings>> GetPerformersSettings(string activityCode)
{
logger.LogDebug("searching for "+activityCode);
var activity = await DbContext.Activities.SingleAsync(a=>a.Code == activityCode);
logger.LogDebug(JsonConvert.SerializeObject(activity));
if (activity.SettingsClassName==null) return null;
var dbSetPropInfo = Startup.UserSettings.SingleOrDefault(s => s.PropertyType.GenericTypeArguments[0].FullName == activity.SettingsClassName);
if (dbSetPropInfo == null) return null;
// var settingType = dbSetPropInfo.PropertyType;
// var dbSetType = typeof(DbSet<>).MakeGenericType(new Type[] { settingType } );
// avec une info method Remove et Update, ça le ferait ...
return (IQueryable<ISpecializationSettings>) dbSetPropInfo.GetValue(DbContext);
}
public async Task<IBillable> GetBillAsync(string billingCode, long queryId)
{
var dbFunc = Startup.Billing[billingCode];
IBillable query = null;
await Task.Run(()=> dbFunc(DbContext, queryId));
return query;
}
public async Task<ISpecializationSettings> GetPerformerSettingsAsync(string activityCode, string userId)
{
return await (await GetPerformersSettings(activityCode)).SingleOrDefaultAsync(s=> s.UserId == userId);
}
public Task<IQueryable<ISpecializationSettings>> GetPerformersSettingsAsync(string activityCode)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,26 @@
namespace Yavsc.Services
{
using System.Linq;
using System.Threading.Tasks;
using Billing;
public interface IBillingService
{
/// <summary>
/// Renvoye la facture associée à une clé de facturation:
/// le couple suivant :
///
/// * un code de facturation
/// (identifiant associé à un type de facturation d'un flux de travail)
/// * un entier long identifiant la demande du client
/// (à une demande, on associe au maximum une seule facture)
/// </summary>
/// <param name="billingCode">Identifiant du type de facturation</param>
/// <param name="queryId">Identifiant de la demande du client</param>
/// <returns>La facture</returns>
Task<IBillable> GetBillAsync(string billingCode, long queryId);
Task<IQueryable<ISpecializationSettings>> GetPerformersSettingsAsync(string activityCode);
Task<ISpecializationSettings> GetPerformerSettingsAsync(string activityCode, string userId);
}
}

@ -7,65 +7,83 @@ using Microsoft.Extensions.Logging;
namespace Yavsc
{
using Exceptions;
using Microsoft.Data.Entity;
using Models;
using Yavsc.Billing;
public partial class Startup
{
/// <summary>
/// Lists Available user profile classes,
/// populated at startup, using reflexion.
/// </summary>
public static List<Type> ProfileTypes = new List<Type>() ;
public static List<PropertyInfo> UserSettings = new List<PropertyInfo> ();
public static List<Type> ProfileTypes = new List<Type>();
public static List<PropertyInfo> UserSettings = new List<PropertyInfo>();
public static Dictionary<string,Func<ApplicationDbContext,long,IBillable>> Billing =
new Dictionary<string,Func<ApplicationDbContext,long,IBillable>> ();
/// <summary>
/// Lists available command forms.
/// This is hard coded.
/// </summary>
public static readonly string [] Forms = new string [] { "Profiles" , "HairCut" };
public static PropertyInfo GetUserSettingPropertyInfo(string settingsClassName)
{
return UserSettings.SingleOrDefault(s => s.PropertyType.GenericTypeArguments[0].FullName == settingsClassName ) ;
}
public static readonly string[] Forms = new string[] { "Profiles", "HairCut" };
private void ConfigureWorkflow(IApplicationBuilder app, SiteSettings settings, ILogger logger)
{
System.AppDomain.CurrentDomain.ResourceResolve += OnYavscResourceResolve;
foreach (var a in System.AppDomain.CurrentDomain.GetAssemblies()) {
foreach (var c in a.GetTypes()) {
foreach (var a in System.AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var c in a.GetTypes())
{
if (c.IsClass && !c.IsAbstract &&
c.GetInterface("ISpecializationSettings")!=null) {
c.GetInterface("ISpecializationSettings") != null)
{
ProfileTypes.Add(c);
}
}
}
foreach (var propinfo in typeof(ApplicationDbContext).GetProperties()) {
foreach (var attr in propinfo.CustomAttributes) {
if (attr.AttributeType == typeof(Yavsc.Attributes.ActivitySettingsAttribute)) {
// bingo
if (typeof(IQueryable<ISpecializationSettings>).IsAssignableFrom(propinfo.PropertyType))
{
logger.LogVerbose($"Paramêtres utilisateur déclaré: {propinfo.Name}");
UserSettings.Add(propinfo);
} else
// Design time error
{
var msg =
$@"La propriété {propinfo.Name} du contexte de la
foreach (var propinfo in typeof(ApplicationDbContext).GetProperties())
{
foreach (var attr in propinfo.CustomAttributes)
{
// something like a DbSet?
if (attr.AttributeType == typeof(Yavsc.Attributes.ActivitySettingsAttribute))
{
// TODO swith () case {}
if (typeof(IQueryable<ISpecializationSettings>).IsAssignableFrom(propinfo.PropertyType))
{// double-bingo
logger.LogVerbose($"Pro: {propinfo.Name}");
UserSettings.Add(propinfo);
}
else
// Design time error
{
var msg =
$@"La propriété {propinfo.Name} du contexte de la
base de donnée porte l'attribut [ActivitySetting],
mais n'implemente pas l'interface IQueryable<ISpecializationSettings>
({propinfo.MemberType.GetType()})";
logger.LogCritical(msg);
throw new InvalidWorkflowModelException(msg);
}
logger.LogCritical(msg);
}
}
}
}
Billing.Add("Brush", new Func<ApplicationDbContext,long,IBillable>
( ( db, id) =>
{
var query = db.HairCutQueries.Include(q=>q.Prestation).Single(q=>q.Id == id) ;
query.SelectedProfile = db.BrusherProfile.Single(b=>b.UserId == query.PerformerId);
return query;
})) ;
Billing.Add("MBrush",new Func<ApplicationDbContext,long,IBillable>
( (db, id) => db.HairMultiCutQueries.Single(q=>q.Id == id)));
Billing.Add("Rdv", new Func<ApplicationDbContext,long,IBillable>
( (db, id) => db.RdvQueries.Single(q=>q.Id == id)));
}
public static System.Reflection.Assembly OnYavscResourceResolve (object sender, ResolveEventArgs ev)
public static System.Reflection.Assembly OnYavscResourceResolve(object sender, ResolveEventArgs ev)
{
return AppDomain.CurrentDomain.GetAssemblies()[0];
}

@ -189,10 +189,9 @@ namespace Yavsc
services.AddSingleton<IAuthorizationHandler, HasTemporaryPassHandler>();
services.AddSingleton<IAuthorizationHandler, BlogEditHandler>();
services.AddSingleton<IAuthorizationHandler, BlogViewHandler>();
services.AddSingleton<IAuthorizationHandler, CommandEditHandler>();
services.AddSingleton<IAuthorizationHandler, CommandViewHandler>();
services.AddSingleton<IAuthorizationHandler, BillEditHandler>();
services.AddSingleton<IAuthorizationHandler, BillViewHandler>();
services.AddSingleton<IAuthorizationHandler, PostUserFileHandler>();
services.AddSingleton<IAuthorizationHandler, EstimateViewHandler>();
services.AddSingleton<IAuthorizationHandler, ViewFileHandler>();
services.AddMvc(config =>
@ -227,6 +226,9 @@ namespace Yavsc
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<IGoogleCloudMessageSender, AuthMessageSender>();
services.AddTransient<IBillingService, BillingService>((servs) =>
new BillingService(servs.GetRequiredService<ILoggerFactory>(), servs.GetService<ApplicationDbContext>())
);
// TODO for SMS: services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddLocalization(options =>
@ -333,9 +335,7 @@ namespace Yavsc
// before fixing the security protocol, let beleive our lib it's done with it.
var cxmgr = ConnectionManager.Instance;
// then, fix it.
ServicePointManager.SecurityProtocol = (SecurityProtocolType) 0xC00;
logger.LogInformation($"ServicePointManager.SecurityProtocol: {ServicePointManager.SecurityProtocol}");
ServicePointManager.SecurityProtocol = (SecurityProtocolType) 0xC00; // Tls12, required by PayPal
app.UseIISPlatformHandler(options =>
{

@ -0,0 +1,108 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Localization;
using Yavsc.Billing;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Services;
using Yavsc.ViewModels.Gen;
namespace Yavsc.ViewComponents
{
public enum OutputFormat {
Html,
LaTeX,
Pdf
}
public class BillViewComponent : ViewComponent
{
ApplicationDbContext dbContext;
IBillingService billingService;
IStringLocalizer<Yavsc.Resources.YavscLocalisation> localizer;
public BillViewComponent(ApplicationDbContext dbContext,
IStringLocalizer<Yavsc.Resources.YavscLocalisation> localizer)
{
this.dbContext = dbContext;
this.localizer = localizer;
}
/*
public async Task<IViewComponentResult> InvokeAsync(string code, long id)
{
return await InvokeAsync(code, id, OutputFormat.Html);
}
public async Task<IViewComponentResult> InvokeAsync(string code, long id, OutputFormat outputFormat)
{
return await InvokeAsync(code,id,outputFormat,false,false);
}
public async Task<IViewComponentResult> InvokeAsync(string code, long id, OutputFormat outputFormat, bool asBill, bool acquitted)
{
var billable = await Task.Run( () => billingService.GetBillAsync(code,id));
if (billable == null)
throw new Exception("No data");
return await InvokeAsync(code, billable, outputFormat,asBill,acquitted);
} */
public async Task<IViewComponentResult> InvokeAsync(string code, IBillable billable, OutputFormat format, bool asBill, bool acquitted)
{
var di = new DirectoryInfo(Startup.SiteSetup.UserFiles.Bills);
var dia = new DirectoryInfo(Startup.SiteSetup.UserFiles.Avatars);
ViewBag.BillsDir = di.FullName;
ViewBag.AvatarsDir = dia.FullName;
ViewBag.AsBill = asBill; // vrai pour une facture, sinon, c'est un devis
ViewBag.Acquitted = acquitted;
ViewBag.BillingCode = code;
switch (format) {
case OutputFormat.LaTeX :
var client = await dbContext.Users
.Include(u=>u.PostalAddress)
.SingleAsync(u=>u.Id == billable.ClientId);
ViewBag.Client = client;
var performer = await dbContext.Users
.Include(u=>u.BankInfo)
.Include(u=>u.PostalAddress)
.SingleAsync(u=>u.Id == billable.PerformerId);
ViewBag.Performer = performer;
ViewBag.ClientAddress = client.PostalAddress?.Address.SplitAddressToTeX();
var profile = await dbContext.Performers
.Include(p=>p.OrganizationAddress)
.SingleAsync(p=>p.PerformerId == billable.PerformerId);
ViewBag.PerformerProfile = profile;
ViewBag.ActivityLabel = localizer[billable.ActivityCode];
ViewBag.PerformerOrganizationAddress = profile.OrganizationAddress.Address.SplitAddressToTeX() ;
ViewBag.PerformerAddress = performer.PostalAddress?.Address.SplitAddressToTeX() ;
return this.View("Bill_tex", billable);
case OutputFormat.Pdf :
string tex = null;
var oldWriter = ViewComponentContext.ViewContext.Writer;
using (var writer = new StringWriter())
{
this.ViewComponentContext.ViewContext.Writer = writer;
var resultTex = View("Bill_tex", billable);
await resultTex.ExecuteAsync(this.ViewComponentContext);
tex = writer.ToString();
}
ViewComponentContext.ViewContext.Writer = oldWriter;
return this.View("Bill_pdf",
new PdfGenerationViewModel{
Temp = Startup.Temp,
TeXSource = tex,
DestDir = Startup.UserBillsDirName,
BaseFileName = $"bill-{code}-{billable.Id}"
} );
}
return View("Default",billable);
}
}
}

@ -1,79 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.Data.Entity;
using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.ViewModels.Gen;
namespace Yavsc.ViewComponents
{
public class EstimateViewComponent : ViewComponent
{
ApplicationDbContext dbContext;
public EstimateViewComponent(ApplicationDbContext dbContext)
{
this.dbContext = dbContext;
}
public async Task<IViewComponentResult> InvokeAsync(long id)
{
return await InvokeAsync(id,"Html");
}
public async Task<IViewComponentResult> InvokeAsync(long id, string outputFormat ="Html")
{
return await InvokeAsync(id,outputFormat,false,false);
}
public async Task<IViewComponentResult> InvokeAsync(long id, string outputFormat ="Html", bool asBill = false, bool acquitted = false)
{
return await Task.Run( ()=> {
Estimate estimate =
dbContext.Estimates.Include(x => x.Query)
.Include(x => x.Query.Client)
.Include(x => x.Query.PerformerProfile)
.Include(x => x.Query.PerformerProfile.OrganizationAddress)
.Include(x => x.Query.PerformerProfile.Performer)
.Include(e => e.Bill).FirstOrDefault(x => x.Id == id);
if (estimate == null)
throw new Exception("No data");
var di = new DirectoryInfo(Startup.SiteSetup.UserFiles.Bills);
var dia = new DirectoryInfo(Startup.SiteSetup.UserFiles.Avatars);
ViewBag.BillsDir = di.FullName;
ViewBag.AvatarsDir = dia.FullName;
ViewBag.AsBill = asBill;
ViewBag.Acquitted = acquitted;
if (outputFormat == "LaTeX") {
return this.View("Estimate_tex", estimate);
}
else if (outputFormat == "Pdf")
{
// Sorry for this code
string tex = null;
var oldWriter = ViewComponentContext.ViewContext.Writer;
using (var writer = new StringWriter())
{
this.ViewComponentContext.ViewContext.Writer = writer;
var resultTex = View("Estimate_tex", estimate);
resultTex.Execute(this.ViewComponentContext);
tex = writer.ToString();
}
ViewComponentContext.ViewContext.Writer = oldWriter;
return this.View("Estimate_pdf",
new PdfGenerationViewModel{
Temp = Startup.Temp,
TeXSource = tex,
DestDir = Startup.UserBillsDirName,
BaseFileName = $"estimate-{id}"
} );
}
return View("Default",estimate);
}
);
}
}
}

@ -1,23 +0,0 @@
using System.Security.Claims;
using Microsoft.AspNet.Authorization;
using Yavsc.Models.Billing;
namespace Yavsc.ViewModels.Auth.Handlers
{
public class EstimateViewHandler : AuthorizationHandler<ViewRequirement, Estimate>
{
protected override void Handle(AuthorizationContext context, ViewRequirement requirement, Estimate resource)
{
if (context.User.IsInRole(Constants.AdminGroupName)
|| context.User.IsInRole(Constants.FrontOfficeGroupName))
context.Succeed(requirement);
else if (context.User.Identity.IsAuthenticated) {
var uid = context.User.GetUserId();
if (resource.OwnerId == uid || resource.ClientId == uid)
context.Succeed(requirement);
// TODO && ( resource.Circles == null || context.User belongs to resource.Circles )
}
}
}
}

@ -6,6 +6,6 @@ namespace Yavsc.ViewModels.Workflow
{
public UserActivity Declaration { get; set; }
public bool NeedsSettings { get; set; }
public bool HasSettings { get; set; }
public ISpecializationSettings Settings { get; set; }
}
}

@ -15,10 +15,10 @@
@if (Model.NeedsSettings) {
<a asp-controller="@ViewBag.ProfileType.Name" asp-action="Index" class="btn btn-default">
@SR[Model.Declaration.Does.SettingsClassName]
@if (!Model.HasSettings) {
<span class="badge">Non renseigné: Cliquez vite<br/> ici pour positionner les<br/> paramêtres de cette activité</span>
@if (Model.Settings==null) {
<span class="badge">Non renseigné: Cliquez vite<br/> ici pour positionner les<br/>
paramêtres de cette activité</span>
}
</a>
}
</dd>

@ -1,4 +1,4 @@
@model HairCutQuery
@model Yavsc.Models.Haircut.HairCutQuery
@{ ViewData["Title"] = $"{ViewBag.Activity.Name}: Votre commande"; }
@await Html.PartialAsync("BrusherProfileScript",ViewData["PerfPrefs"])
@ -186,8 +186,8 @@
var pos = loc.geometry.location;
var lat = new Number(pos.lat);
var lng = new Number(pos.lng);
$('#' + config.latId).val(lat.toLocaleString('en'));
$('#' + config.longId).val(lng.toLocaleString('en'));
$('#' + config.latId).val(lat);
$('#' + config.longId).val(lng);
gmap.setCenter(pos);
if (marker) {
marker.setMap(null);
@ -335,7 +335,6 @@
</div>
</div>
</div>
<div class="form-group">
<label for="EventDate" class="col-md-2 control-label">
@SR["Event date"]

@ -0,0 +1,22 @@
@using System.Reflection
@using System.IO
@using Microsoft.Extensions.WebEncoders
@using System.Diagnostics
@using System.Text
@using Yavsc.Formatters
@using Yavsc.Helpers
@model Yavsc.ViewModels.Gen.PdfGenerationViewModel
@{
Model.GenerateEstimatePdf();
}
@if (Model.Generated) {
<div>
@(Model.BaseFileName).pdf was generated
</div>
} else {
<div class="error">
@Model.GenerationErrorMessage
<p>Something went wrong ...</p>
</div>
}

@ -0,0 +1,216 @@
@using Yavsc.Helpers
@using System.IO
@using System.Globalization
@model IBillable
@{
Layout = null;
var pro = ViewBag.PerformerProfile;
var from = ViewBag.Performer;
var to = ViewBag.Client;
var PostalAddress = ViewBag.ClientAddress;
var proaddr = ViewBag.PerformerOrganizationAddress;
var proaddrm = proaddr;
var isestimate = !ViewBag.AsBill;
var prosign = new FileInfo($"{ViewBag.BillsDir}/sign-{ViewBag.BillingCode}-{Model.Id}.png");
var clisign = new FileInfo($"{ViewBag.BillsDir}/sign-{ViewBag.BillingCode}-{Model.Id}.png");
var activity = ViewBag.ActivityLabel;
var bill = Model.GetBillItems();
string validationDate = null ;
if (Model.ValidationDate.HasValue) {
validationDate = Model.ValidationDate.Value.ToString("dddd dd MMMM yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
}
}
\documentclass[french,11pt]{article}
\usepackage{eurosym}
\usepackage{babel}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[a4paper]{geometry}
\usepackage{units}
\usepackage{bera}
\usepackage{graphicx}
\usepackage{fancyhdr}
\usepackage{fp}
\def\TVA{20} % Taux de la TVA
\def\TotalHT{0}
\def\TotalTVA{0}
\newcommand{\AjouterService}[3]{% Arguments : Désignation, quantité, prix
\FPround{\prix}{#3}{2}
\FPeval{\montant}{#2 * #3}
\FPround{\montant}{\montant}{2}
\FPadd{\TotalHT}{\TotalHT}{\montant}
\eaddto\ListeProduits{#1 & \prix & #2 & \montant \cr}
}
\newcommand{\AfficheResultat}{%
\ListeProduits
\FPeval{\TotalTVA}{\TotalHT * \TVA / 100}
\FPadd{\TotalTTC}{\TotalHT}{\TotalTVA}
\FPround{\TotalHT}{\TotalHT}{2}
\FPround{\TotalTVA}{\TotalTVA}{2}
\FPround{\TotalTTC}{\TotalTTC}{2}
\global\let\TotalHT\TotalHT
\global\let\TotalTVA\TotalTVA
\global\let\TotalTTC\TotalTTC
\cr
\hline
\textbf{Total} & & & \TotalHT
}
\newcommand*\eaddto[2]{% version développée de \addto
\edef\tmp{#2}%
\expandafter\addto
\expandafter#1%
\expandafter{\tmp}%
}
\newcommand{\ListeProduits}{}
%%%%%%%%%%%%%%%%%%%%% A MODIFIER DANS LA FACTURE %%%%%%%%%%%%%%%%%%%%%
\def\FactureNum {@Model.Id.ToString()} % Numéro de facture
\def\FactureAcquittee {@ViewBag.Acquitted?"oui":"non"} % Facture acquittée : oui/non
\def\FactureLieu {@proaddrm} % Lieu de l'édition de la facture
\def\FactureObjet {@(new HtmlString(isestimate?"Devis":"Facture")) en @TeXHelpers.ToTeX(activity)} % Objet du document
% Description de la facture
\def\FactureDescr {
@TeXHelpers.ToTeX(Model.GetDescription())
}
% Infos Client
\def\ClientNom{@TeXHelpers.ToTeX(to.UserName)} % Nom du client
\def\ClientAdresse{
% Adresse du client
@PostalAddress
@if (PostalAddress!=null) {<text>@PostalAddress</text>}
@if (!string.IsNullOrWhiteSpace(to.PhoneNumber)) {<text>@TeXHelpers.ToTeX(to.PhoneNumber)<text>\\</text>
</text>}
E-mail: @TeXHelpers.ToTeX(to.Email)
}
% Liste des produits facturés : Désignation, prix
@foreach (var line in bill) {
<text>\AjouterService{@TeXHelpers.ToTeX(line.Description)}{@line.Count}{@line.UnitaryCost.ToString("F2",CultureInfo.InvariantCulture)}
</text>}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\geometry{verbose,tmargin=4em,bmargin=8em,lmargin=6em,rmargin=6em}
\setlength{\parindent}{0pt}
\setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
\thispagestyle{fancy}
\pagestyle{fancy}
\setlength{\parindent}{0pt}
\renewcommand{\headrulewidth}{0pt}
\cfoot{ @TeXHelpers.ToTeX(from.UserName) @if (proaddrm!=null) { <text> - @proaddrm </text> } \newline
\small{ E-mail: @TeXHelpers.ToTeX(from.Email) @if (!string.IsNullOrWhiteSpace(from.PhoneNumber)) { <text> - Téléphone fixe: @TeXHelpers.ToTeX(from.PhoneNumber) </text> }
}
}
\begin{document}
% Logo de la société
@if (from.Avatar != null) {
<text>\includegraphics[height=60pt]{@(ViewBag.AvatarsDir)/@(from.UserName).png}
</text>
} else {
<text>%\includegraphics{logo.png}
</text>
}
% Nom et adresse de la société
@TeXHelpers.ToTeX(from.UserName) \\
@proaddr
@(isestimate?"Devis":"Facture") n°\FactureNum
{\addtolength{\leftskip}{10.5cm} %in ERT
\textbf{\ClientNom} \\
\ClientAdresse \\
} %in ERT
\hspace*{10.5cm}
\FactureLieu, le \today
~\\~\\
\textbf{Objet : \FactureObjet \\}
\textnormal{\FactureDescr}
~\\
\begin{center}
\begin{tabular}{lrrr}
\textbf{Désignation ~~~~~~} & \textbf{Prix unitaire} & \textbf{Quantité} & \textbf{Montant (\euro)} \\
\hline
\AfficheResultat{}
\end{tabular}
\end{center}
\begin{flushright}
\textit{Auto entreprise en franchise de TVA}\\
\end{flushright}
~\\
@if (ViewBag.AsBill) {
} else if (ViewBag.Acquitted) {
<text>
\ifthenelse{\equal{\FactureAcquittee}{oui}}{
Facture acquittée.
}
</text>
} else {
var bi = from.BankInfo;
if (bi!=null) {
<text>À régler par chèque ou par virement bancaire :
\begin{center}
\begin{tabular}{|c c c c|}
@if (!string.IsNullOrWhiteSpace(bi.BankCode) && !string.IsNullOrWhiteSpace(bi.WicketCode)
&& !string.IsNullOrWhiteSpace(bi.AccountNumber) ) {
<text>\hline \textbf{Code banque} & \textbf{Code guichet} & \textbf{N° de Compte} & \textbf{Clé RIB} \\
@bi.BankCode & @bi.WicketCode & @bi.AccountNumber & @bi.BankedKey \\
</text>
}
@if (!string.IsNullOrWhiteSpace(@bi.IBAN) && !string.IsNullOrWhiteSpace(@bi.BIC)) {
<text>
\hline \textbf{IBAN N°} & \multicolumn{3}{|l|}{ @bi.IBAN } \\
\hline \textbf{Code BIC} & \multicolumn{3}{|l|}{ @bi.BIC }
</text>
} \\
\hline
\end{tabular}
\end{center}</text>
}
}
@if (validationDate!=null) {
<text>
\begin{flushright}
@(new TeXString(isestimate?"Devis demandé le ":"Facture suite à la demande du ")+
TeXHelpers.ToTeX(validationDate))
\end{flushright}
@if (prosign.Exists) {
<text>
\begin{center}
\hspace{263pt}
\includegraphics[height=60pt]{@(ViewBag.BillsDir)/estimate-prosign-@(Model.Id).png}
\end{center}
</text>
}
</text>
}
\end{document}

@ -0,0 +1,13 @@
@model IBillable
<div class="bill">
<a href="~/api/pdfbill/bill-@(Model.ActivityCode)-@(Model.Id).tex" >Export au format LaTeX</a>
<form action="~/api/pdfbill/gen/@(Model.ActivityCode)-@Model.Id" method="POST">
<input type="submit" value="Générer le Pdf"/>
</form>
<a href="~/api/pdfbill/get/@(Model.Id)" >Télécharger le document généré</a>
</div>

@ -2,12 +2,12 @@
@model Estimate
<div class="estimate">
<a href="~/api/pdfestimate/estimate-@(Model.Id).tex" >Export au format LaTeX</a>
<a href="~/api/pdfbill/estimate-@(Model.Id).tex" >Export au format LaTeX</a>
<form action="~/api/pdfestimate/gen/@Model.Id" method="POST">
<form action="~/api/pdfbill/gen/@Model.Id" method="POST">
<input type="submit" value="Générer le Pdf"/>
</form>
<a href="~/api/pdfestimate/get/@(Model.Id)" >Télécharger le document généré</a>
<a href="~/api/pdfbill/get/@(Model.Id)" >Télécharger le document généré</a>
</div>

@ -191,8 +191,7 @@
@if (!(Model.ProviderValidationDate==null)) {
<text>
\begin{flushright}
@(new HtmlString(isestimate?"Devis validé":"Facture validée")) le @TeXHelpers.ToTeX(Model.ProviderValidationDate.ToString("dddd dd MMMM yyyy",
CultureInfo.CreateSpecificCulture("fr-FR")))
@(new HtmlString(isestimate?"Devis validé":"Facture validée")) le @TeXHelpers.ToTeX(Model.ProviderValidationDate.ToString("dddd dd MMMM yyyy"))
\end{flushright}
@if (prosign.Exists) {
<text>

Some files were not shown because too many files have changed in this diff Show More

Loading…